From 3314e147d7d6ef31174af8bf91d412df1b1893af Mon Sep 17 00:00:00 2001 From: NirmalKumarYuvaraj <97871636+NirmalKumarYuvaraj@users.noreply.github.com> Date: Mon, 24 Mar 2025 04:56:20 +0530 Subject: [PATCH 01/18] Fixed Keyboard Scrolling in editors with Center or End VerticalTextAlignment (#25827) * Fixed Keyboard scrolling in editors with Center or end VerticalTextAlignment is off * Modified the code change to consider content offset value * Removed unwanted file changes * Removed test cases * Modified changes * Modified the code changes and included UI test * Removed unwanted code changes * updated changes * optimized changes * updated test case * Added a button for Start * Changes in MauiTextView --------- Co-authored-by: Matthew Leibowitz Co-authored-by: Dhivya-SF4094 <127717131+Dhivya-SF4094@users.noreply.github.com> --- .../TestCases.HostApp/Issues/Issue24977.cs | 146 ++++++++++++++++++ .../Tests/Issues/Issue24977.cs | 74 +++++++++ .../Platform/iOS/KeyboardAutoManagerScroll.cs | 2 +- src/Core/src/Platform/iOS/MauiTextView.cs | 13 ++ 4 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue24977.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24977.cs diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue24977.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue24977.cs new file mode 100644 index 000000000000..d55ef4f10198 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue24977.cs @@ -0,0 +1,146 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 24977, "Keyboard Scrolling in editors with Center or End VerticalTextAlignment is off", PlatformAffected.iOS)] +public class Issue24977 : TestNavigationPage +{ + protected override void Init() + { + PushAsync(new ContentPage + { + Content = new VerticalStackLayout + { + Spacing = 12, + Padding = new Thickness(12, 24), + Children = + { + CreateButton(TextAlignment.Start), + CreateButton(TextAlignment.Center), + CreateButton(TextAlignment.End), + } + } + }); + } + + Button CreateButton(TextAlignment textAlignment) + { + var btn = new Button + { + Text = textAlignment.ToString(), + AutomationId = $"{textAlignment}Button" + }; + btn.Clicked += (s, e) => Navigation.PushAsync(new Issue24977_1(textAlignment)); + return btn; + } +} + +public class Issue24977_1 : TestContentPage +{ + Editor editor; + Label cursorHeightTracker; + + public Issue24977_1(TextAlignment textAlignment) + { + editor.VerticalTextAlignment = textAlignment; + } + + protected override void Init() + { + var rootGrid = new Grid + { + RowDefinitions = + { + new RowDefinition { Height = 50 }, + new RowDefinition { Height = 50 }, + new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, + new RowDefinition { Height = 50 } + }, + Margin = new Thickness(30) + }; + + var entry = new Entry + { + Text = "Content before", + AutomationId = "EntryBefore", + ReturnType = ReturnType.Next, + BackgroundColor = Colors.Aquamarine, + }; + + cursorHeightTracker = new Label + { + Text = "0", + AutomationId = "CursorHeightTracker", + BackgroundColor = Colors.Aquamarine, + }; + + editor = new Editor + { + Text = "Hello World!", + AutomationId = "IssueEditor", + BackgroundColor = Colors.Orange, + }; + editor.TextChanged += Editor_TextChanged; + + var button = new Button { Text = "Erase" }; + button.Clicked += (s, e) => editor.Text = string.Empty; + + rootGrid.Add(entry, 0, 0); + rootGrid.Add(cursorHeightTracker, 0, 1); + rootGrid.Add(editor, 0, 2); + + rootGrid.Add(button, 0, 3); + + Content = rootGrid; + } + + private void Editor_TextChanged(object sender, TextChangedEventArgs e) + { + if (sender is Editor editor) + { + AddCursorHeightToLabel(editor); + } + } + + void AddCursorHeightToLabel(Editor editor) + { +#if IOS + var textInput = editor.Handler.PlatformView as UIKit.UITextView; + var selectedTextRange = textInput?.SelectedTextRange; + var localCursor = selectedTextRange is not null ? textInput?.GetCaretRectForPosition(selectedTextRange.Start) : null; + + if (localCursor is CoreGraphics.CGRect local && textInput is not null) + { + var container = GetContainerView(textInput); + var cursorInContainer = container.ConvertRectFromView(local, textInput); + var cursorInWindow = container.ConvertRectToView(cursorInContainer, null); + + cursorHeightTracker.Text = cursorInWindow.Y.ToString(); + } + + UIKit.UIView GetContainerView(UIKit.UIView startingPoint) + { + var rootView = FindResponder(startingPoint)?.View; + + if (rootView is not null) + { + return rootView; + } + + return null; + } + + T FindResponder(UIKit.UIView view) + where T : UIKit.UIResponder + { + var nextResponder = view as UIKit.UIResponder; + while (nextResponder is not null) + { + nextResponder = nextResponder.NextResponder; + + if (nextResponder is T responder) + return responder; + } + return null; + } +#endif + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24977.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24977.cs new file mode 100644 index 000000000000..b51f785f790f --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24977.cs @@ -0,0 +1,74 @@ +#if IOS +using System.Drawing; +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; +using System.Text; + +namespace Microsoft.Maui.TestCases.Tests.Issues; +public class Issue24977 : _IssuesUITest +{ + public Issue24977(TestDevice device) : base(device) { } + + public override string Issue => "Keyboard Scrolling in editors with Center or End VerticalTextAlignment is off"; + + [Test] + [Category(UITestCategories.Editor)] + public void KeepEditorCursorAboveKeyboardWithVerticalAlignmentAsCenter() + { + TestKeepEditorCursorAboveKeyboard("CenterButton", true); + } + + [Test] + [Category(UITestCategories.Editor)] + public void KeepEditorCursorAboveKeyboardWithVerticalAlignmentAsEnd() + { + TestKeepEditorCursorAboveKeyboard("EndButton"); + } + + void TestKeepEditorCursorAboveKeyboard(string buttonId, bool fromCenter = false) + { + App.WaitForElement(buttonId); + App.Tap(buttonId); + Thread.Sleep(1000); + + var app = App as AppiumApp; + if (app == null) + return; + + var editorRect = app.WaitForElement("IssueEditor").GetRect(); + app.Click("IssueEditor"); + + var sb = new StringBuilder(); + for (int i = 1; i <= 20; i++) + { + sb.Append($"\n{i}"); + } + + app.EnterText("IssueEditor", sb.ToString()); + + var keyboardLocation = KeyboardScrolling.FindiOSKeyboardLocation(app.Driver); + + var cursorLabel = app.WaitForElement("CursorHeightTracker").GetText(); + var cursorHeight1 = Convert.ToDouble(cursorLabel); + try + { + if (keyboardLocation is Point keyboardPoint) + { + Assert.That(cursorHeight1 < keyboardPoint.Y); + } + else + { + Assert.Fail("keyboardLocation is null"); + } + } + finally + { + if (fromCenter) + { + App.Back(); + } + } + } +} +#endif \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs b/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs index a041ee679d76..85865b5df29a 100644 --- a/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs +++ b/src/Core/src/Platform/iOS/KeyboardAutoManagerScroll.cs @@ -24,7 +24,7 @@ public static class KeyboardAutoManagerScroll static CGPoint StartingContentOffset; static UIEdgeInsets StartingScrollIndicatorInsets; static UIEdgeInsets StartingContentInsets; - static CGRect KeyboardFrame = CGRect.Empty; + internal static CGRect KeyboardFrame = CGRect.Empty; static CGPoint TopViewBeginOrigin = new(nfloat.MaxValue, nfloat.MaxValue); static readonly CGPoint InvalidPoint = new(nfloat.MaxValue, nfloat.MaxValue); static double AnimationDuration = 0.25; diff --git a/src/Core/src/Platform/iOS/MauiTextView.cs b/src/Core/src/Platform/iOS/MauiTextView.cs index 80a561e7b2c6..abd5be6475b1 100644 --- a/src/Core/src/Platform/iOS/MauiTextView.cs +++ b/src/Core/src/Platform/iOS/MauiTextView.cs @@ -172,6 +172,19 @@ void ShouldCenterVertically() Maui.TextAlignment.End => new CGPoint(0, -Math.Max(1, availableSpace)), _ => ContentOffset, }; + + // Scroll the content to the cursor position if it is hidden by the keyboard + if (KeyboardAutoManagerScroll.IsKeyboardShowing && (VerticalTextAlignment == Maui.TextAlignment.Center || VerticalTextAlignment == Maui.TextAlignment.End)) + { + var cursorRect = KeyboardAutoManagerScroll.FindCursorPosition(); + var keyboardTop = KeyboardAutoManagerScroll.KeyboardFrame.Top; + + if (cursorRect.HasValue && cursorRect.Value.Bottom > keyboardTop) + { + var offset = cursorRect.Value.Bottom - KeyboardAutoManagerScroll.KeyboardFrame.Top; + ContentOffset = new CGPoint(ContentOffset.X, ContentOffset.Y + offset); + } + } } void UpdatePlaceholderFont(UIFont? value) From 61991e646a12a2e48ef8c9bd4acc56af2a8bfce0 Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Mon, 24 Mar 2025 00:55:21 +0100 Subject: [PATCH 02/18] [XC] better support for nullable props and BPs (#28550) --- .../src/Build.Tasks/SetPropertiesVisitor.cs | 61 +++++++++++++------ .../Xaml.UnitTests/Issues/Bz24910.xaml.cs | 7 ++- .../Xaml.UnitTests/Issues/Unreported008.xaml | 38 ++++++------ .../Issues/Unreported008.xaml.cs | 13 ++++ 4 files changed, 82 insertions(+), 37 deletions(-) diff --git a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs index 537fbb0698d3..a51cf6a2c0a9 100644 --- a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs +++ b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs @@ -1410,6 +1410,25 @@ static IEnumerable SetBinding(VariableDefinition parent, FieldRefer static bool CanSetValue(FieldReference bpRef, bool attached, INode node, IXmlLineInfo iXmlLineInfo, ILContext context) { + static bool CanSetValue (TypeReference bpTypeRef, VariableDefinition varValue, ILContext context, IXmlLineInfo iXmlLineInfo) + { + // If it's an attached BP, there's no second chance to handle IMarkupExtensions, so we try here. + // Worst case scenario ? InvalidCastException at runtime + if (varValue.VariableType.FullName == "System.Object") + return true; + var implicitOperator = varValue.VariableType.GetImplicitOperatorTo(context.Cache, bpTypeRef, context.Body.Method.Module); + if (implicitOperator != null) + return true; + + //as we're in the SetValue Scenario, we can accept value types, they'll be boxed + if (varValue.VariableType.IsValueType && bpTypeRef.FullName == "System.Object") + return true; + + if (varValue.VariableType.InheritsFromOrImplements(context.Cache, bpTypeRef) || varValue.VariableType.FullName == "System.Object") + return true; + return false; + } + var module = context.Body.Method.Module; if (bpRef == null) @@ -1424,22 +1443,23 @@ static bool CanSetValue(FieldReference bpRef, bool attached, INode node, IXmlLin if (!context.Variables.TryGetValue(elementNode, out VariableDefinition varValue)) return false; + var bpTypeRef = bpRef.GetBindablePropertyType(context.Cache, iXmlLineInfo, module); - // If it's an attached BP, there's no second chance to handle IMarkupExtensions, so we try here. - // Worst case scenario ? InvalidCastException at runtime - if (varValue.VariableType.FullName == "System.Object") - return true; - var implicitOperator = varValue.VariableType.GetImplicitOperatorTo(context.Cache, bpTypeRef, module); - if (implicitOperator != null) - return true; - //as we're in the SetValue Scenario, we can accept value types, they'll be boxed - if (varValue.VariableType.IsValueType && bpTypeRef.FullName == "System.Object") + if (CanSetValue(bpTypeRef, varValue, context, iXmlLineInfo)) return true; - return varValue.VariableType.InheritsFromOrImplements(context.Cache, bpTypeRef) || varValue.VariableType.FullName == "System.Object"; + if (bpTypeRef.ResolveCached(context.Cache).FullName == "System.Nullable`1") + { + bpTypeRef = ((GenericInstanceType)bpTypeRef).GenericArguments[0]; + if (CanSetValue(bpTypeRef, varValue, context, iXmlLineInfo)) + return true; + } + + return false; } + static bool CanGetValue(VariableDefinition parent, FieldReference bpRef, bool attached, IXmlLineInfo iXmlLineInfo, ILContext context, out TypeReference propertyType) { var module = context.Body.Method.Module; @@ -1483,32 +1503,37 @@ static IEnumerable SetValue(VariableDefinition parent, FieldReferen var @else = Create(OpCodes.Nop); var endif = Create(OpCodes.Nop); - if (context.Variables[elementNode].VariableType.FullName == "System.Object") + + var varValue = context.Variables[elementNode]; + if (varValue.VariableType.FullName == "System.Object") { //if(value != null && value.GetType().IsAssignableFrom(typeof(BindingBase))) - yield return Create(Ldloc, context.Variables[elementNode]); + yield return Create(Ldloc, varValue); yield return Create(Brfalse, @else); - yield return Create(Ldtoken, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindingBase"))); yield return Create(Call, module.ImportMethodReference(context.Cache, ("mscorlib", "System", "Type"), methodName: "GetTypeFromHandle", parameterTypes: new[] { ("mscorlib", "System", "RuntimeTypeHandle") }, isStatic: true)); - yield return Create(Ldloc, context.Variables[elementNode]); + yield return Create(Ldloc, varValue); yield return Create(Callvirt, module.ImportMethodReference(context.Cache, ("mscorlib", "System", "Object"), methodName: "GetType", paramCount: 0)); yield return Create(Callvirt, module.ImportMethodReference(context.Cache, ("mscorlib", "System", "Type"), methodName: "IsAssignableFrom", parameterTypes: new[] { ("mscorlib", "System", "Type") })); yield return Create(Brfalse, @else); //then - yield return Create(Ldloc, context.Variables[elementNode]); + yield return Create(Ldloc, varValue); yield return Create(Br, endif); //else yield return @else; } var bpTypeRef = bpRef.GetBindablePropertyType(context.Cache, iXmlLineInfo, module); - foreach (var instruction in context.Variables[elementNode].LoadAs(context.Cache, bpTypeRef, module)) + foreach (var instruction in varValue.LoadAs(context.Cache, bpTypeRef, module)) yield return instruction; if (bpTypeRef.IsValueType) + { + if ( bpTypeRef.ResolveCached(context.Cache).FullName == "System.Nullable`1" + && TypeRefComparer.Default.Equals(varValue.VariableType, ((GenericInstanceType)bpTypeRef).GenericArguments[0])) + bpTypeRef = ((GenericInstanceType)bpTypeRef).GenericArguments[0]; yield return Create(Box, module.ImportReference(bpTypeRef)); - + } //endif - if (context.Variables[elementNode].VariableType.FullName == "System.Object") + if (varValue.VariableType.FullName == "System.Object") yield return endif; } diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Bz24910.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Bz24910.xaml.cs index fc4c19ae7ab5..a820be8ef0d2 100644 --- a/src/Controls/tests/Xaml.UnitTests/Issues/Bz24910.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Bz24910.xaml.cs @@ -47,6 +47,9 @@ public void ConversionForNullable(bool useCompiledXaml) [TestCase(true), TestCase(false)] public void AllowNull(bool useCompiledXaml) { + if (useCompiledXaml) + MockCompiler.Compile(typeof(Bz24910)); + var page = new Bz24910(useCompiledXaml); var control = page.control2; Assert.Null(control.NullableInt); @@ -85,7 +88,7 @@ public void AllowNonBindableNullable(bool useCompiledXaml) public class Bz24910Control : Button { public static readonly BindableProperty NullableIntProperty = - BindableProperty.Create("NullableInt", typeof(int?), typeof(Bz24910Control), default(int?)); + BindableProperty.Create(nameof(NullableInt), typeof(int?), typeof(Bz24910Control), default(int?)); public int? NullableInt { @@ -94,7 +97,7 @@ public int? NullableInt } public static readonly BindableProperty NullableDoubleProperty = - BindableProperty.Create("NullableDouble", typeof(double?), typeof(Bz24910Control), default(double?)); + BindableProperty.Create(nameof(NullableDouble), typeof(double?), typeof(Bz24910Control), default(double?)); public double? NullableDouble { diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Unreported008.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Unreported008.xaml index fac231519002..896c9873ec14 100644 --- a/src/Controls/tests/Xaml.UnitTests/Issues/Unreported008.xaml +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Unreported008.xaml @@ -2,22 +2,26 @@ - - yyyy-MM-dd - - - - Jan 1 2000 - - - - - - - Dec 31 2050 - - - - + + + yyyy-MM-dd + + + + Jan 1 2000 + + + + + + + Dec 31 2050 + + + + + + \ No newline at end of file diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Unreported008.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Unreported008.xaml.cs index 5866e5458d4a..410ff8fbab4d 100644 --- a/src/Controls/tests/Xaml.UnitTests/Issues/Unreported008.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Unreported008.xaml.cs @@ -4,6 +4,17 @@ namespace Microsoft.Maui.Controls.Xaml.UnitTests { + public class Unreported008View : ContentView + { + public static readonly BindableProperty DateProperty = BindableProperty.Create(nameof(Date), typeof(DateTime?), typeof(Unreported008View), null); + + public DateTime? Date + { + get { return (DateTime?)GetValue(DateProperty); } + set { SetValue(DateProperty, value); } + } + } + public partial class Unreported008 : ContentPage { public Unreported008() @@ -27,6 +38,8 @@ public void PickerDateTimesAndXamlC(bool useCompiledXaml) Assert.AreEqual(DateTime.Today, picker.Date.Date); Assert.AreEqual(new DateTime(2000, 1, 1), picker.MinimumDate); Assert.AreEqual(new DateTime(2050, 12, 31), picker.MaximumDate); + + Assert.AreEqual(DateTime.Today, page.view0.Date.Value.Date); } } } From 4c09dd7f60a041dd63e68ca1508f0731ae4ee0bc Mon Sep 17 00:00:00 2001 From: Jakub Florkowski <42434498+kubaflo@users.noreply.github.com> Date: Mon, 24 Mar 2025 00:56:25 +0100 Subject: [PATCH 03/18] [iOS] Changing carousel view orientation with disabled loop - fix (#28545) * [iOS] Changing carousel view orientation with disabled loop - fix * Added snapshots --- .../Items/iOS/CarouselViewController.cs | 14 +++++++++ .../PublicAPI/net-ios/PublicAPI.Unshipped.txt | 1 + .../net-maccatalyst/PublicAPI.Unshipped.txt | 1 + .../CarouselViewItemShouldScaleProperly.png | Bin 0 -> 15478 bytes .../TestCases.HostApp/Issues/Issue28523.xaml | 28 ++++++++++++++++++ .../Issues/Issue28523.xaml.cs | 11 +++++++ .../Tests/Issues/Issue28523.cs | 25 ++++++++++++++++ .../CarouselViewItemShouldScaleProperly.png | Bin 0 -> 15528 bytes 8 files changed, 80 insertions(+) create mode 100644 src/Controls/tests/TestCases.Android.Tests/snapshots/android/CarouselViewItemShouldScaleProperly.png create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue28523.xaml create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue28523.xaml.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28523.cs create mode 100644 src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/CarouselViewItemShouldScaleProperly.png diff --git a/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs index ca07824e4a11..afa9ea62f139 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/CarouselViewController.cs @@ -32,6 +32,20 @@ public class CarouselViewController : ItemsViewController bool _isRotating; + public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection) + { + if (previousTraitCollection.VerticalSizeClass == TraitCollection.VerticalSizeClass) + { + return; + } + + if (ItemsView?.Loop == false || _carouselViewLoopManager is null) + { + CollectionView.ReloadData(); + InitialPositionSet = false; + } + } + public CarouselViewController(CarouselView itemsView, ItemsViewLayout layout) : base(itemsView, layout) { CollectionView.AllowsSelection = false; diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt index e33dcb76f5a2..9089f402c400 100644 --- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -71,6 +71,7 @@ Microsoft.Maui.Controls.HybridWebView.SetInvokeJavaScriptTarget(T! target) -> ~Microsoft.Maui.Controls.Xaml.IXamlTypeResolver.Resolve(string qualifiedTypeName, System.IServiceProvider serviceProvider = null, bool expandToExtension = true) -> System.Type ~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.RequireServiceAttribute(System.Type[] serviceTypes) -> void ~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.ServiceTypes.get -> System.Type[] +~override Microsoft.Maui.Controls.Handlers.Items.CarouselViewController.TraitCollectionDidChange(UIKit.UITraitCollection previousTraitCollection) -> void ~override Microsoft.Maui.Controls.Handlers.Items2.CarouselViewController2.CreateDelegator() -> UIKit.UICollectionViewDelegateFlowLayout ~override Microsoft.Maui.Controls.Handlers.Items2.CarouselViewController2.CreateItemsViewSource() -> Microsoft.Maui.Controls.Handlers.Items.IItemsViewSource ~override Microsoft.Maui.Controls.Handlers.Items2.CarouselViewController2.DetermineCellReuseId(Foundation.NSIndexPath indexPath) -> string diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 6950a4815676..44b640bc304f 100644 --- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -71,6 +71,7 @@ Microsoft.Maui.Controls.HybridWebView.SetInvokeJavaScriptTarget(T! target) -> ~Microsoft.Maui.Controls.Xaml.IXamlTypeResolver.Resolve(string qualifiedTypeName, System.IServiceProvider serviceProvider = null, bool expandToExtension = true) -> System.Type ~Microsoft.Maui.Controls.WebViewProcessTerminatedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformWebViewProcessTerminatedEventArgs ~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.RequireServiceAttribute(System.Type[] serviceTypes) -> void +~override Microsoft.Maui.Controls.Handlers.Items.CarouselViewController.TraitCollectionDidChange(UIKit.UITraitCollection previousTraitCollection) -> void ~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.ServiceTypes.get -> System.Type[] ~override Microsoft.Maui.Controls.Handlers.Items2.CarouselViewController2.CreateDelegator() -> UIKit.UICollectionViewDelegateFlowLayout ~override Microsoft.Maui.Controls.Handlers.Items2.CarouselViewController2.CreateItemsViewSource() -> Microsoft.Maui.Controls.Handlers.Items.IItemsViewSource diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/CarouselViewItemShouldScaleProperly.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/CarouselViewItemShouldScaleProperly.png new file mode 100644 index 0000000000000000000000000000000000000000..daf55575bde69605b5325321f9d46e4169bed078 GIT binary patch literal 15478 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 + + + + + + + + + + + Baboon + Capuchin Monkey + Blue Monkey + Squirrel Monkey + Golden Lion Tamarin + Howler Monkey + + + + \ No newline at end of file diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue28523.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue28523.xaml.cs new file mode 100644 index 000000000000..0836b66b0283 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue28523.xaml.cs @@ -0,0 +1,11 @@ +namespace Maui.Controls.Sample.Issues +{ + [Issue(IssueTracker.Github, 28523, "Different behavior on iOS and Android when Loop = False", PlatformAffected.iOS)] + public partial class Issue28523 : ContentPage + { + public Issue28523() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28523.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28523.cs new file mode 100644 index 000000000000..0af63c3a42af --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28523.cs @@ -0,0 +1,25 @@ +#if ANDROID || IOS +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; +public class Issue28523 : _IssuesUITest +{ + public Issue28523(TestDevice testDevice) : base(testDevice) + { + } + + public override string Issue => "Different behavior on iOS and Android when Loop = False"; + + [Test] + [Category(UITestCategories.CarouselView)] + public void CarouselViewItemShouldScaleProperly() + { + App.WaitForElement("Baboon"); + App.SetOrientationLandscape(); + App.WaitForElement("Baboon"); + VerifyScreenshot(); + } +} +#endif \ No newline at end of file diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/CarouselViewItemShouldScaleProperly.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/CarouselViewItemShouldScaleProperly.png new file mode 100644 index 0000000000000000000000000000000000000000..6f97047fc856bfab57204f52280608376f8f45c4 GIT binary patch literal 15528 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 Pq6R=to_kC8969q(_)yiT literal 0 HcmV?d00001 From ef98fa45a6bad8c301e72a1200a957c6cbb08484 Mon Sep 17 00:00:00 2001 From: Vignesh-SF3580 <102575140+Vignesh-SF3580@users.noreply.github.com> Date: Mon, 24 Mar 2025 05:40:57 +0530 Subject: [PATCH 04/18] Fixed ScrollView Orientation Neither issue (#27231) * Fixed - 18418 : ScrollView Orientation=Neither not working * updated ScrollViewerExtensions.cs * Disable the scroll in ios when orientation is neither. * updated ScrollViewHandler.iOS.cs * testcases updated. * Apply suggestions from code review * Apply suggestions from code review --------- Co-authored-by: Gerald Versluis --- .../Issues/Issue2680ScrollView.cs | 29 ++++++++++--- .../Tests/Issues/Issue2680ScrollView.cs | 23 +++-------- .../ScrollView/ScrollViewHandler.Windows.cs | 2 +- .../ScrollView/ScrollViewHandler.iOS.cs | 8 ++++ .../Windows/ScrollViewerExtensions.cs | 41 +++++++++++-------- .../src/Platform/iOS/ScrollViewExtensions.cs | 9 +++- 6 files changed, 70 insertions(+), 42 deletions(-) diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue2680ScrollView.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue2680ScrollView.cs index a2d374f19c8f..5ac041126626 100644 --- a/src/Controls/tests/TestCases.HostApp/Issues/Issue2680ScrollView.cs +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue2680ScrollView.cs @@ -16,6 +16,11 @@ public void ToggleButtonText() protected override void Init() { + var contentView = new ContentView + { + HeightRequest = 2000 + }; + // Initialize ui here instead of ctor var longStackLayout = new StackLayout(); @@ -24,21 +29,26 @@ protected override void Init() longStackLayout.Children.Add(toggleButton); - longStackLayout.Children.Add(new Label - { - Text = "First label", - AutomationId = FirstItemMark - }); + firstItemLabel = new Label + { + Text = "Not scrolled", + AutomationId = FirstItemMark + }; + + longStackLayout.Children.Add(firstItemLabel); Enumerable.Range(2, 50).Select(i => new Label { Text = $"Test label {i}" }) .ToList().ForEach(label => longStackLayout.Children.Add(label)); + contentView.Content = longStackLayout; + scrollView = new ScrollView { Orientation = ScrollOrientation.Neither, AutomationId = ScrollViewMark, - Content = longStackLayout + Content = contentView }; + scrollView.Scrolled += ScrollViewOnScrolled; Content = scrollView; } @@ -48,9 +58,16 @@ void ToggleButtonOnClicked(object sender, EventArgs e) scrollView.Orientation = IsScrollEnabled ? ScrollOrientation.Vertical : ScrollOrientation.Neither; } + void ScrollViewOnScrolled(object sender, ScrolledEventArgs e) + { + firstItemLabel.Text = "Scrolled"; + } + ScrollView scrollView; Button toggleButton; + Label firstItemLabel; + const string ScrollViewMark = "ScrollView"; const string FirstItemMark = "FirstItem"; const string ToggleButtonMark = "ToggleButton"; diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue2680ScrollView.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue2680ScrollView.cs index 5c64d85c88d5..e46b79e7d961 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue2680ScrollView.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue2680ScrollView.cs @@ -1,6 +1,4 @@ -#if TEST_FAILS_ON_WINDOWS && TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST // In iOS and Catalyst, WaitForNoElement throws a timeout exception eventhough the text is not visible on the screen by scrolling. -//In Windows, The ScrollView remains scrollable even when ScrollOrientation.Neither is set. Issue Link: https://github.com/dotnet/maui/issues/27140 -using NUnit.Framework; +using NUnit.Framework; using UITest.Appium; using UITest.Core; @@ -20,32 +18,21 @@ public Issue2680ScrollView(TestDevice testDevice) : base(testDevice) [Test] [Category(UITestCategories.ScrollView)] - [FailsOnIOSWhenRunningOnXamarinUITest] - [FailsOnMacWhenRunningOnXamarinUITest] public void Issue2680Test_ScrollDisabled() { - App.WaitForElement(ScrollViewMark); + var label = App.WaitForElement(FirstItemMark); App.ScrollDown(ScrollViewMark); - App.ScrollDown(ScrollViewMark); - - App.WaitForElement(FirstItemMark, timeout: TimeSpan.FromSeconds(5)); + Assert.That(label.GetText(), Is.EqualTo("Not scrolled")); } [Test] [Category(UITestCategories.ScrollView)] - [FailsOnIOSWhenRunningOnXamarinUITest] - [FailsOnMacWhenRunningOnXamarinUITest] - [FailsOnWindowsWhenRunningOnXamarinUITest] public void Issue2680Test_ScrollEnabled() { - App.WaitForElement(ToggleButtonMark); + var label = App.WaitForElement(FirstItemMark); App.Tap(ToggleButtonMark); - App.ScrollDown(ScrollViewMark); - App.ScrollDown(ScrollViewMark); - - App.WaitForNoElement(FirstItemMark, timeout: TimeSpan.FromSeconds(5)); + Assert.That(label.GetText(), Is.EqualTo("Scrolled")); } } } -#endif \ No newline at end of file diff --git a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.Windows.cs b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.Windows.cs index 0309f84bb32e..1a6752f2e4a0 100644 --- a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.Windows.cs +++ b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.Windows.cs @@ -62,7 +62,7 @@ public static void MapHorizontalScrollBarVisibility(IScrollViewHandler handler, public static void MapVerticalScrollBarVisibility(IScrollViewHandler handler, IScrollView scrollView) { - handler.PlatformView.VerticalScrollBarVisibility = scrollView.VerticalScrollBarVisibility.ToWindowsScrollBarVisibility(); + handler.PlatformView?.UpdateScrollBarVisibility(scrollView.Orientation, scrollView.VerticalScrollBarVisibility); } public static void MapOrientation(IScrollViewHandler handler, IScrollView scrollView) diff --git a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs index 7c5883653223..c8e7d6620fba 100644 --- a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs +++ b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs @@ -83,6 +83,14 @@ public static void MapOrientation(IScrollViewHandler handler, IScrollView scroll return; } + if (scrollView.Orientation == ScrollOrientation.Neither) + { + platformView.ScrollEnabled = false; + } + else + { + platformView.ScrollEnabled = true; + } platformView.InvalidateMeasure(scrollView); } diff --git a/src/Core/src/Platform/Windows/ScrollViewerExtensions.cs b/src/Core/src/Platform/Windows/ScrollViewerExtensions.cs index d04e1dedd633..81350563f0bf 100644 --- a/src/Core/src/Platform/Windows/ScrollViewerExtensions.cs +++ b/src/Core/src/Platform/Windows/ScrollViewerExtensions.cs @@ -19,35 +19,44 @@ public static WScrollBarVisibility ToWindowsScrollBarVisibility(this ScrollBarVi public static void UpdateScrollBarVisibility(this ScrollViewer scrollViewer, ScrollOrientation orientation, ScrollBarVisibility horizontalScrollBarVisibility) { + if (orientation == ScrollOrientation.Neither) + { + scrollViewer.HorizontalScrollBarVisibility = scrollViewer.VerticalScrollBarVisibility = WScrollBarVisibility.Disabled; + return; + } + if (horizontalScrollBarVisibility == ScrollBarVisibility.Default) { // If the user has not explicitly set a horizontal scroll bar visibility, then the orientation will // determine what the horizontal scroll bar does - scrollViewer.HorizontalScrollBarVisibility = orientation switch { ScrollOrientation.Horizontal or ScrollOrientation.Both => WScrollBarVisibility.Auto, _ => WScrollBarVisibility.Disabled, }; - return; + scrollViewer.VerticalScrollBarVisibility = orientation switch + { + ScrollOrientation.Vertical or ScrollOrientation.Both => WScrollBarVisibility.Auto, + _ => WScrollBarVisibility.Disabled, + }; } - - // If the user _has_ set a horizontal scroll bar visibility preference, then convert that preference to the native equivalent - // if the orientation allows for it - - scrollViewer.HorizontalScrollBarVisibility = orientation switch + else { - ScrollOrientation.Horizontal or ScrollOrientation.Both => horizontalScrollBarVisibility.ToWindowsScrollBarVisibility(), - _ => WScrollBarVisibility.Disabled, - }; - - // TODO ezhart 2021-07-08 RE: the note below - do we actually need to be accounting for Neither in the measurement code? - // Could we just disable the scroll bars entirely? - // Accounting for Neither in the xplat measurement code is a leftover from Forms, it may be easier to do that on the native side here. + // If the user _has_ set a horizontal scroll bar visibility preference, then convert that preference to the native equivalent + // if the orientation allows for it + scrollViewer.HorizontalScrollBarVisibility = orientation switch + { + ScrollOrientation.Horizontal or ScrollOrientation.Both => horizontalScrollBarVisibility.ToWindowsScrollBarVisibility(), + _ => WScrollBarVisibility.Disabled, + }; - // Note that the Orientation setting of "Neither" is covered by the measurement code (the size of the content is limited - // so that no scrolling is possible) and the xplat scrolling code (the ScrollTo methods are disabled when Orientation=Neither) + scrollViewer.VerticalScrollBarVisibility = orientation switch + { + ScrollOrientation.Vertical or ScrollOrientation.Both => horizontalScrollBarVisibility.ToWindowsScrollBarVisibility(), + _ => WScrollBarVisibility.Disabled, + }; + } } public static void UpdateContent(this ScrollViewer scrollViewer, IView? content, IMauiContext context) diff --git a/src/Core/src/Platform/iOS/ScrollViewExtensions.cs b/src/Core/src/Platform/iOS/ScrollViewExtensions.cs index 98a7a9ba4686..02c65a3d10e6 100644 --- a/src/Core/src/Platform/iOS/ScrollViewExtensions.cs +++ b/src/Core/src/Platform/iOS/ScrollViewExtensions.cs @@ -52,7 +52,14 @@ public static void UpdateContentSize(this UIScrollView scrollView, Size contentS public static void UpdateIsEnabled(this UIScrollView nativeScrollView, IScrollView scrollView) { - nativeScrollView.ScrollEnabled = scrollView.IsEnabled; + if (scrollView.Orientation == ScrollOrientation.Neither) + { + nativeScrollView.ScrollEnabled = false; + } + else + { + nativeScrollView.ScrollEnabled = scrollView.IsEnabled; + } } } } From 055d9ecae1e9c0d62d8c071c89a4e8ec4b3ac2b4 Mon Sep 17 00:00:00 2001 From: SuthiYuvaraj <92777079+SuthiYuvaraj@users.noreply.github.com> Date: Mon, 24 Mar 2025 18:30:45 +0530 Subject: [PATCH 05/18] Fix HideSoftInputOnTapped Not Working Net9 (#28534) * fix for 26792 * Update HideSoftInputOnTappedChangedManager.Platform.cs * Testcases added * Comments updated * Condition modification --- ...SoftInputOnTappedChangedManager.Android.cs | 2 +- ...oftInputOnTappedChangedManager.Platform.cs | 2 +- .../HideSoftInputOnTappedChangedManager.cs | 2 +- .../TestCases.HostApp/Issues/Issue26792.cs | 40 +++++++++++++++++++ .../Tests/Issues/Issue26792.cs | 29 ++++++++++++++ 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue26792.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26792.cs diff --git a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Android.cs b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Android.cs index 3dc75e168a60..3501898dcfcf 100644 --- a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Android.cs +++ b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Android.cs @@ -20,7 +20,7 @@ void OnWindowDispatchedTouch(object? sender, MotionEvent? e) foreach (var page in _contentPages) { - if (page.HasNavigatedTo && + if ((page.HasNavigatedTo || page.Parent is Window) && page.HideSoftInputOnTapped && page.Handler is IPlatformViewHandler pvh && pvh.MauiContext?.Context is not null) diff --git a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Platform.cs b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Platform.cs index b5ed8c838a64..45b49052c65b 100644 --- a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Platform.cs +++ b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Platform.cs @@ -14,7 +14,7 @@ partial class HideSoftInputOnTappedChangedManager internal void UpdatePage(ContentPage page) { - if (page.HideSoftInputOnTapped && page.HasNavigatedTo) + if (page.HideSoftInputOnTapped && (page.HasNavigatedTo || page.Parent is Window)) { if (!_contentPages.Contains(page)) { diff --git a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.cs b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.cs index 1f369165f5f8..17f1c41e75d7 100644 --- a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.cs +++ b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.cs @@ -13,7 +13,7 @@ bool FeatureEnabled { foreach (var page in _contentPages) { - if (page.HideSoftInputOnTapped && page.HasNavigatedTo) + if (page.HideSoftInputOnTapped && (page.HasNavigatedTo || page.Parent is Window)) return true; } return false; diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue26792.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue26792.cs new file mode 100644 index 000000000000..cb4045de9468 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue26792.cs @@ -0,0 +1,40 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 26792, "HideSoftInputOnTapped Not Working", PlatformAffected.Android)] +public class Issue26792 : ContentPage +{ + + VerticalStackLayout stackLayout; + Button _button; + + Entry _entry; + public Issue26792() + { + this.HideSoftInputOnTapped = true; + stackLayout = new VerticalStackLayout + { + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + AutomationId = "Issue26792StackLayout", + }; + _button = new Button + { + Text = "Click Me", + AutomationId = "Issue26792Button", + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + }; + _entry = new Entry + { + Placeholder = "Enter text", + AutomationId = "Issue26792Entry", + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + }; + + stackLayout.Children.Add(_button); + stackLayout.Children.Add(_entry); + Content = stackLayout; + } +} + diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26792.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26792.cs new file mode 100644 index 000000000000..88bd8968731c --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26792.cs @@ -0,0 +1,29 @@ +#if TEST_FAILS_ON_CATALYST && TEST_FAILS_ON_WINDOWS //As SoftKeyboard is not supported +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue26792 : _IssuesUITest +{ + public Issue26792(TestDevice testDevice) : base(testDevice) + { + } + + public override string Issue => "HideSoftInputOnTapped Not Working"; + + [Test] + [Category(UITestCategories.Page)] + public void SoftInputShouldHiddenOnTap() + { + App.WaitForElement("Issue26792Entry"); + App.Tap("Issue26792Entry"); + App.WaitForElement("Issue26792Button"); + App.Tap("Issue26792Button"); + App.WaitForElement("Issue26792Entry"); + Assert.That(App.IsKeyboardShown(), Is.False); + + } +} +#endif From 99f3cc5c2c92d8cfed117ee74f62442d8bcb1e7e Mon Sep 17 00:00:00 2001 From: Anandhan Rajagopal <97146406+anandhan-rajagopal@users.noreply.github.com> Date: Wed, 26 Mar 2025 19:15:55 +0530 Subject: [PATCH 06/18] [Testing] Migration of Compatibility.Core platform-specific unit tests into device tests - 3 (#28103) * Added the CornerRadius and IsEnabled property for DeviceTest * IsEnabled property for Device tests * Addressed the feedbacks * Update BoxViewTests.cs * modified code changes * resolved windows build error * Update RadioButtonTests.Windows.cs * Update CheckBoxTests.iOS.cs * removed unwanted namespace * Update CheckBoxTests.cs * Update SwipeViewTests.Android.cs * Update BoxViewTests.Android.cs * Resolved build error in windows * Update RadioButtonTests.Windows.cs * Added the CornerRadius and IsEnabled property for DeviceTest * IsEnabled property for Device tests * Addressed the feedbacks * Update BoxViewTests.cs * modified code changes * resolved windows build error * Update RadioButtonTests.Windows.cs * Update CheckBoxTests.iOS.cs * removed unwanted namespace * Update CheckBoxTests.cs * Update SwipeViewTests.Android.cs * Resolved build error in windows * Update RadioButtonTests.Windows.cs * Update CheckBoxTests.Android.cs * Resolved conflict errors * Update BoxViewTests.Android.cs --------- Co-authored-by: nivetha-nagalingam --- .../Elements/BoxView/BoxViewTests.Android.cs | 21 +++++++++ .../Elements/BoxView/BoxViewTests.Windows.cs | 23 ++++++++++ .../Elements/BoxView/BoxViewTests.cs | 17 ++++++- .../Elements/Button/ButtonTests.iOS.cs | 20 ++++++++ .../CheckBox/CheckBoxTests.Android.cs | 16 +++++++ .../CheckBox/CheckBoxTests.Windows.cs | 17 +++++++ .../Elements/CheckBox/CheckBoxTests.iOS.cs | 17 +++++++ .../Elements/Editor/EditorTests.Android.cs | 20 ++++++++ .../Elements/Label/LabelTests.Android.cs | 20 ++++++++ .../RadioButton/RadioButtonTests.Windows.cs | 46 ++++++++++++++++++- .../SearchBar/SearchBarTests.Android.cs | 20 ++++++++ .../SearchBar/SearchBarTests.Windows.cs | 22 +++++++++ .../SwipeView/SwipeViewTests.Android.cs | 19 ++++++++ 13 files changed, 275 insertions(+), 3 deletions(-) diff --git a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs index 0d84d49eb8e3..1add2de04889 100644 --- a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Android.cs @@ -2,11 +2,13 @@ using System.ComponentModel; using System.Threading.Tasks; using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Handlers; using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; using Xunit; + namespace Microsoft.Maui.DeviceTests { public partial class BoxViewTests @@ -96,6 +98,25 @@ public async Task RotationConsistent() var platformRotation = await InvokeOnMainThreadAsync(() => platformBoxView.Rotation); Assert.Equal(expected, platformRotation); } + + [Fact] + [Description("The IsEnabled property of a BoxView should match with native IsEnabled")] + public async Task VerifyBoxViewIsEnabledProperty() + { + var boxView = new BoxView + { + IsEnabled = false + }; + var expectedValue = boxView.IsEnabled; + + var handler = await CreateHandlerAsync(boxView); + var nativeView = GetNativeBoxView(handler); + await InvokeOnMainThreadAsync(() => + { + var isEnabled = nativeView.Enabled; + Assert.Equal(expectedValue, isEnabled); + }); + } Task GetPlatformIsVisible(ShapeViewHandler boxViewViewHandler) { diff --git a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs index 88e0735833b1..da087c8ae9d7 100644 --- a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.Windows.cs @@ -3,6 +3,10 @@ using Microsoft.Maui.Graphics.Platform; using Microsoft.Maui.Graphics.Win2D; using Microsoft.Maui.Handlers; +using Xunit; +using System.ComponentModel; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Handlers; namespace Microsoft.Maui.DeviceTests { @@ -20,6 +24,25 @@ Task GetPlatformOpacity(ShapeViewHandler handler) }); } + [Fact] + [Description("The IsEnabled property of a BoxView should match with native IsEnabled")] + public async Task BoxViewIsEnabled() + { + var boxView = new BoxView + { + IsEnabled = false + }; + var expectedValue = boxView.IsEnabled; + + var handler = await CreateHandlerAsync(boxView); + var nativeView = GetNativeBoxView(handler); + await InvokeOnMainThreadAsync(() => + { + var isEnabled = nativeView.IsEnabled; + Assert.Equal(expectedValue, isEnabled); + }); + } + Task GetPlatformIsVisible(ShapeViewHandler boxViewHandler) { return InvokeOnMainThreadAsync(() => diff --git a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs index cf574048faf1..5f271391ad18 100644 --- a/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/BoxView/BoxViewTests.cs @@ -4,9 +4,9 @@ using Microsoft.Maui.Controls; using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; +using Microsoft.Maui.Controls.Handlers; using Microsoft.Maui.Hosting; using Xunit; -using Microsoft.Maui.Controls.Handlers; namespace Microsoft.Maui.DeviceTests { @@ -47,6 +47,21 @@ public async Task BoxViewBackgroundColorConsistent() await ValidateHasColor(boxView, expected, typeof(ShapeViewHandler)); } + [Fact] + [Description("The Background of a BoxView should match with native Background")] + public async Task BoxViewBackgroundConsistent() + { + var boxView = new BoxView + { + HeightRequest = 100, + WidthRequest = 200, + Background = Brush.Red + }; + var expected = (boxView.Background as SolidColorBrush)?.Color; + + await ValidateHasColor(boxView, expected, typeof(BoxViewHandler)); + } + [Fact] [Description("The Opacity property of a BoxView should match with native Opacity")] public async Task VerifyBoxViewOpacityProperty() diff --git a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs index 0b1f27eef54b..b16315e26605 100644 --- a/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/Button/ButtonTests.iOS.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.ComponentModel; using System.Threading.Tasks; using Microsoft.Maui.Controls; using Microsoft.Maui.Graphics; @@ -87,5 +88,24 @@ await InvokeOnMainThreadAsync(async () => Assert.True(button.Width < gridWidth, $"Button shouldn't occupy entire layout width. Expected: {gridWidth}<, was {button.Width}"); Assert.True(button.Height < gridHeight, $"Button shouldn't occupy entire layout height. Expected: {gridHeight}<, was {button.Height}"); } + + [Fact] + [Description("The CornerRadius of a Button should match with native CornerRadius")] + public async Task ButtonCornerRadius() + { + var button = new Button + { + CornerRadius = 15, + }; + var expectedValue = button.CornerRadius; + + var handler = await CreateHandlerAsync(button); + var nativeView = GetPlatformButton(handler); + await InvokeOnMainThreadAsync(() => + { + var platformCornerRadius = nativeView.Layer.CornerRadius; + Assert.Equal(expectedValue, platformCornerRadius); + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs index 7403625f6268..e31c3eab2fa5 100644 --- a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Android.cs @@ -98,6 +98,22 @@ public async Task RotationConsistent() Assert.Equal(expected, platformRotation); } + [Fact("The IsEnabled of a CheckBox should match with native IsEnabled")] + public async Task CheckBoxIsEnabled() + { + var checkBox = new CheckBox(); + checkBox.IsEnabled = false; + var expectedValue = checkBox.IsEnabled; + + var handler = await CreateHandlerAsync(checkBox); + var nativeView = GetNativeCheckBox(handler); + await InvokeOnMainThreadAsync(() => + { + var isEnabled = nativeView.Enabled; + Assert.Equal(expectedValue, isEnabled); + }); + } + Task GetPlatformIsVisible(CheckBoxHandler checkBoxHandler) { return InvokeOnMainThreadAsync(() => diff --git a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs index b37682a406a4..da7a3b43ae4f 100644 --- a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.Windows.cs @@ -3,6 +3,7 @@ using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; using Microsoft.UI.Xaml.Controls; +using Xunit; namespace Microsoft.Maui.DeviceTests { @@ -19,6 +20,22 @@ Task GetPlatformOpacity(CheckBoxHandler checkBoxHandler) return (float)nativeView.Opacity; }); } + + [Fact("The IsEnabled of a CheckBox should match with native IsEnabled")] + public async Task CheckBoxIsEnabled() + { + var checkBox = new Microsoft.Maui.Controls.CheckBox(); + checkBox.IsEnabled = false; + var expectedValue = checkBox.IsEnabled; + + var handler = await CreateHandlerAsync(checkBox); + var nativeView = GetNativeCheckBox(handler); + await InvokeOnMainThreadAsync(() => + { + var isEnabled = nativeView.IsEnabled; + Assert.Equal(expectedValue, isEnabled); + }); + } Task GetPlatformIsVisible(CheckBoxHandler checkBoxHandler) { diff --git a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs index c907dcf606ff..c1cbbfe2a5a5 100644 --- a/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/CheckBox/CheckBoxTests.iOS.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Microsoft.Maui.Graphics; +using Microsoft.Maui.Controls; using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; using Xunit; @@ -21,6 +22,22 @@ Task GetPlatformOpacity(CheckBoxHandler checkBoxHandler) return (float)nativeView.Alpha; }); } + + [Fact("The IsEnabled of a CheckBox should match with native IsEnabled")] + public async Task CheckBoxIsEnabled() + { + var checkBox = new CheckBox(); + checkBox.IsEnabled = false; + var expectedValue = checkBox.IsEnabled; + + var handler = await CreateHandlerAsync(checkBox); + var nativeView = GetNativeCheckBox(handler); + await InvokeOnMainThreadAsync(() => + { + var isEnabled = nativeView.Enabled; + Assert.Equal(expectedValue, isEnabled); + }); + } Task GetPlatformIsVisible(CheckBoxHandler checkBoxHandler) { diff --git a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Android.cs index 00b457c78179..0df2cb8c285a 100644 --- a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Android.cs @@ -150,5 +150,25 @@ public async Task RotationConsistent() var platformRotation = await InvokeOnMainThreadAsync(() => PlatformEditor.Rotation); Assert.Equal(expected, platformRotation); } + + [Fact] + [Description("The IsEnabled property of a Editor should match with native IsEnabled")] + public async Task VerifyEditorIsEnabledProperty() + { + var editor = new Editor + { + IsEnabled = false + }; + var expectedValue = editor.IsEnabled; + + var handler = await CreateHandlerAsync(editor); + var nativeView = GetPlatformControl(handler); + await InvokeOnMainThreadAsync(() => + { + var isEnabled = nativeView.Enabled; + + Assert.Equal(expectedValue, isEnabled); + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Android.cs index 03e1aadb80aa..e584366a7174 100644 --- a/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/Label/LabelTests.Android.cs @@ -162,6 +162,26 @@ public async Task RotationConsistent() var platformRotation = await InvokeOnMainThreadAsync(() => platformLabel.Rotation); Assert.Equal(expected, platformRotation); } + + [Fact] + [Description("The IsEnabled property of a Label should match with native IsEnabled")] + public async Task VerifyLabelIsEnabledProperty() + { + var label = new Label + { + IsEnabled = false + }; + var expectedValue = label.IsEnabled; + + var handler = await CreateHandlerAsync(label); + var nativeView = GetPlatformLabel(handler); + await InvokeOnMainThreadAsync(() => + { + var isEnabled = nativeView.Enabled; + Assert.Equal(expectedValue, isEnabled); + }); + } + TextView GetPlatformLabel(LabelHandler labelHandler) => labelHandler.PlatformView; diff --git a/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs index 30853568b48e..c4097d6720aa 100644 --- a/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using Xunit; using System.Threading.Tasks; using Microsoft.Maui.Controls; using Microsoft.Maui.Handlers; @@ -27,12 +28,54 @@ public async Task VerifyRadioButtonOpacityProperty() var handler = await CreateHandlerAsync(radioButton); var nativeView = GetNativeRadioButton(handler); await InvokeOnMainThreadAsync(() => - { + { var nativeOpacityValue = (float)nativeView.Opacity; Assert.Equal(expectedValue, nativeOpacityValue); }); } + [Fact] + [Description("The CornerRadius of a RadioButton should match with native CornerRadius")] + public async Task RadioButtonCornerRadius() + { + var radioButton = new RadioButton(); + radioButton.CornerRadius = 15; + var expectedValue = radioButton.CornerRadius; + + var handler = await CreateHandlerAsync(radioButton); + var nativeView = GetNativeRadioButton(handler); + await InvokeOnMainThreadAsync(() => + { + var cornerRadiusTopLeft = (float)nativeView.CornerRadius.TopLeft; + var cornerRadiusTopRight = (float)nativeView.CornerRadius.TopRight; + var cornerRadiusBottomLeft = (float)nativeView.CornerRadius.BottomLeft; + var cornerRadiusBottomRight = (float)nativeView.CornerRadius.BottomRight; + Assert.Equal(expectedValue, cornerRadiusTopLeft); + Assert.Equal(expectedValue, cornerRadiusTopRight); + Assert.Equal(expectedValue, cornerRadiusBottomLeft); + Assert.Equal(expectedValue, cornerRadiusBottomRight); + }); + } + + [Fact] + [Description("The IsEnabled of a RadioButton should match with native IsEnabled")] + public async Task VerifyRadioButtonIsEnabledProperty() + { + var radioButton = new RadioButton + { + IsEnabled = false + }; + var expectedValue = radioButton.IsEnabled; + + var handler = await CreateHandlerAsync(radioButton); + var nativeView = GetNativeRadioButton(handler); + await InvokeOnMainThreadAsync(() => + { + var isEnabled = nativeView.IsEnabled; + Assert.Equal(expectedValue, isEnabled); + }); + } + [Fact] [Description("The IsVisible property of a RadioButton should match with native IsVisible")] public async Task VerifyRadioButtonIsVisibleProperty() @@ -50,4 +93,3 @@ await InvokeOnMainThreadAsync(() => }); } } -} diff --git a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Android.cs index 335b55cebb19..8d573064ac4b 100644 --- a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Android.cs @@ -117,6 +117,26 @@ public async Task RotationConsistent() Assert.Equal(expected, platformRotation); } + [Fact] + [Description("The IsEnabled of a SearchBar should match with native IsEnabled")] + public async Task VerifySearchBarIsEnabledProperty() + { + var searchBar = new SearchBar + { + IsEnabled = false + }; + var expectedValue = searchBar.IsEnabled; + + var handler = await CreateHandlerAsync(searchBar); + var nativeView = GetPlatformControl(handler); + await InvokeOnMainThreadAsync(() => + { + var isEnabled = nativeView.Enabled; + + Assert.Equal(expectedValue, isEnabled); + }); + } + Task GetPlatformIsVisible(SearchBarHandler searchBarHandler) { return InvokeOnMainThreadAsync(() => diff --git a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Windows.cs index 30a2ed3c6d6a..3fbf5f0cdfdf 100644 --- a/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/SearchBar/SearchBarTests.Windows.cs @@ -1,8 +1,11 @@ #nullable enable +using System.ComponentModel; using System.Threading.Tasks; +using Microsoft.Maui.Controls; using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; using Microsoft.UI.Xaml.Controls; +using Xunit; namespace Microsoft.Maui.DeviceTests { @@ -52,6 +55,25 @@ Task GetPlatformOpacity(SearchBarHandler searchBarHandler) return (float)nativeView.Opacity; }); } + + [Fact] + [Description("The IsEnabled of a SearchBar should match with native IsEnabled")] + public async Task VerifySearchBarIsEnabledProperty() + { + var searchBar = new SearchBar + { + IsEnabled = false + }; + var expectedValue = searchBar.IsEnabled; + + var handler = await CreateHandlerAsync(searchBar); + var nativeView = GetPlatformControl(handler); + await InvokeOnMainThreadAsync(() => + { + var isEnabled = nativeView.IsEnabled; + Assert.Equal(expectedValue, isEnabled); + }); + } Task GetPlatformIsVisible(SearchBarHandler searchBarHandler) { diff --git a/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.Android.cs index 37ed82a30149..da925a7dfb15 100644 --- a/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.Android.cs +++ b/src/Controls/tests/DeviceTests/Elements/SwipeView/SwipeViewTests.Android.cs @@ -189,5 +189,24 @@ await InvokeOnMainThreadAsync(() => Assert.Equal(expectedValue, isVisible); }); } + + [Fact] + [Description("The IsEnabled of a SwipeView should match with native IsEnabled")] + public async Task VerifySwipeViewIsEnabledProperty() + { + var swipeView = new SwipeView + { + IsEnabled = false + }; + var expectedValue = swipeView.IsEnabled; + + var handler = await CreateHandlerAsync(swipeView); + var nativeView = GetPlatformControl(handler); + await InvokeOnMainThreadAsync(() => + { + var isEnabled = nativeView.Enabled; + Assert.Equal(expectedValue, isEnabled); + }); + } } } \ No newline at end of file From d0c2ac6bf645339b74b4e71cd7aad7690c49072f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Thu, 27 Mar 2025 19:36:09 +0100 Subject: [PATCH 07/18] Fix mistake in RadioButtonTests.Windows.cs --- .../Elements/RadioButton/RadioButtonTests.Windows.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs index c4097d6720aa..84e80f647bcd 100644 --- a/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/RadioButton/RadioButtonTests.Windows.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Microsoft.Maui.Controls; using Microsoft.Maui.Handlers; -using Xunit; namespace Microsoft.Maui.DeviceTests { @@ -77,7 +76,7 @@ await InvokeOnMainThreadAsync(() => } [Fact] - [Description("The IsVisible property of a RadioButton should match with native IsVisible")] + [Description("The IsVisible property of a RadioButton should match with native IsVisible")] public async Task VerifyRadioButtonIsVisibleProperty() { var radioButton = new RadioButton(); @@ -90,6 +89,7 @@ await InvokeOnMainThreadAsync(() => { var isVisible = nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; Assert.Equal(expectedValue, isVisible); - }); + }); } } +} \ No newline at end of file From a9108aa691ec2ecd527bbe521e60b570db5d6ee2 Mon Sep 17 00:00:00 2001 From: NirmalKumarYuvaraj <97871636+NirmalKumarYuvaraj@users.noreply.github.com> Date: Fri, 28 Mar 2025 21:28:41 +0530 Subject: [PATCH 08/18] [Windows] Flyout Menu Icon disappears from Window Title Bar after Navigation (#28240) * Fixed drawer toggle button visiblity * Update ToolbarExtensions.cs * added test cases * added shell test cases * Update NavigationPageToolbar.cs --- .../NavigationPage/NavigationPageToolbar.cs | 6 +- .../Windows/Extensions/ToolbarExtensions.cs | 6 +- src/Controls/src/Core/ShellToolbar.cs | 4 ++ .../Issues/Issue28130_flyout.cs | 57 +++++++++++++++++++ .../Issues/Issue28130_shell.cs | 30 ++++++++++ .../Tests/Issues/Issue24547.cs | 6 +- .../Tests/Issues/Issue28130_flyout.cs | 27 +++++++++ .../Tests/Issues/Issue28130_shell.cs | 28 +++++++++ 8 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue28130_flyout.cs create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue28130_shell.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28130_flyout.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28130_shell.cs diff --git a/src/Controls/src/Core/NavigationPage/NavigationPageToolbar.cs b/src/Controls/src/Core/NavigationPage/NavigationPageToolbar.cs index e6cc1113cb59..cb679d4fc62c 100644 --- a/src/Controls/src/Core/NavigationPage/NavigationPageToolbar.cs +++ b/src/Controls/src/Core/NavigationPage/NavigationPageToolbar.cs @@ -180,7 +180,11 @@ void UpdateBackButton() // Set this before BackButtonVisible triggers an update to the handler // This way all useful information is present - if (Parent is FlyoutPage flyout && flyout.ShouldShowToolbarButton() && !anyPagesPushed.Value) + if (Parent is FlyoutPage flyout && flyout.ShouldShowToolbarButton() +#if !WINDOWS // TODO NET 10 : Move this logic to ShouldShowToolbarButton + && !anyPagesPushed.Value +#endif + ) _drawerToggleVisible = true; else _drawerToggleVisible = false; diff --git a/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs b/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs index 1fe23332fa72..7348dbd80992 100644 --- a/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs +++ b/src/Controls/src/Core/Platform/Windows/Extensions/ToolbarExtensions.cs @@ -87,9 +87,9 @@ public static void UpdateToolbarDynamicOverflowEnabled(this MauiToolbar platform private static void UpdateBackButtonVisibility(MauiToolbar platformToolbar, Toolbar toolbar) { - platformToolbar.IsBackButtonVisible = - toolbar.BackButtonVisible - ? NavigationViewBackButtonVisible.Visible + platformToolbar.IsBackButtonVisible = + toolbar.BackButtonVisible + ? NavigationViewBackButtonVisible.Visible : NavigationViewBackButtonVisible.Collapsed; } } diff --git a/src/Controls/src/Core/ShellToolbar.cs b/src/Controls/src/Core/ShellToolbar.cs index 2b3ebfb63c6a..beb4e6374a62 100644 --- a/src/Controls/src/Core/ShellToolbar.cs +++ b/src/Controls/src/Core/ShellToolbar.cs @@ -91,7 +91,11 @@ internal void ApplyChanges() } var flyoutBehavior = (_shell as IFlyoutView).FlyoutBehavior; +#if WINDOWS + _drawerToggleVisible = flyoutBehavior is FlyoutBehavior.Flyout; +#else _drawerToggleVisible = stack.Count <= 1 && flyoutBehavior is FlyoutBehavior.Flyout; +#endif BackButtonVisible = backButtonVisible && stack.Count > 1; BackButtonEnabled = _backButtonBehavior?.IsEnabled ?? true; ToolbarItems = _toolbarTracker.ToolbarItems; diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue28130_flyout.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue28130_flyout.cs new file mode 100644 index 000000000000..e143f740b7d5 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue28130_flyout.cs @@ -0,0 +1,57 @@ +namespace Maui.Controls.Sample.Issues +{ + [Issue(IssueTracker.Github, 28130, "[Windows] Flyout Menu Icon disappears from Window Title Bar after Navigation", PlatformAffected.UWP)] + public class Issue28130_Flyout : TestFlyoutPage + { + NavigationPage _navigationPage; + string pageTitle = "Issue28130"; + protected override void Init() + { + Flyout = CreateFlyoutContent(); + FlyoutLayoutBehavior = FlyoutLayoutBehavior.Popover; + Detail = _navigationPage = new NavigationPage(new Issue28130_DetailPage(pageTitle) { AutomationId = pageTitle}); + } + + ContentPage CreateFlyoutContent() + { + var flyoutContent = new ContentPage() { Title = "Menu"}; + var layout = new VerticalStackLayout(); + var navigateButton = new Button() { Text = "Navigate to Page 2", AutomationId = "NavigateButton"}; + navigateButton.Clicked += (s, e) => _navigationPage.PushAsync(new Issue28130_Page1()); + layout.Add(navigateButton); + flyoutContent.Content = layout; + return flyoutContent; + } + } + + public class Issue28130_Page1 : TestContentPage + { + protected override void Init() + { + Content = new VerticalStackLayout() + { + new Label() + { + Text = "Tap flyout", + AutomationId="newPageLabel" + } + }; + } + } + + public class Issue28130_DetailPage : ContentPage + { + public Issue28130_DetailPage(string title) + { + Title = title; + Content = new VerticalStackLayout() + { + new Label() + { + Text = "Once Loaded, Tap the flyout", + AutomationId = "detailLabel" + } + }; + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue28130_shell.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue28130_shell.cs new file mode 100644 index 000000000000..7d7adac12c32 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue28130_shell.cs @@ -0,0 +1,30 @@ +namespace Maui.Controls.Sample.Issues +{ + [Issue(IssueTracker.None,28130, "[Windows] Shell Flyout Menu Icon disappears from Window Title Bar after Navigation", PlatformAffected.UWP)] + public class Issue28130_Shell : TestShell + { + + protected override void Init() + { + Routing.RegisterRoute(nameof(Issue28130_Page1), typeof(Issue28130_Page1)); + FlyoutBehavior = FlyoutBehavior.Flyout; + FlyoutWidth = 300; + ItemTemplate = new DataTemplate(() => CreateShellItemTemplate()); + ShellItem shellItem = new ShellItem() { Title = "Issue28130" }; + shellItem.Items.Add(new ShellContent() { Content = new Issue28130_DetailPage("Issue28130") }); + Items.Add(shellItem); + } + + Button CreateShellItemTemplate() + { + var navigateButton = new Button() { Text = "Navigate to Page 2" ,AutomationId = "NavigateButton"}; + navigateButton.Clicked += NavigateButton_Clicked; + return navigateButton; + } + + private void NavigateButton_Clicked(object sender, EventArgs e) + { + GoToAsync(nameof(Issue28130_Page1)); + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24547.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24547.cs index d4db591fb568..d448f7b27a76 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24547.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue24547.cs @@ -1,5 +1,4 @@ -#if TEST_FAILS_ON_WINDOWS // Fix reverted. Refer For further info - https://github.com/dotnet/maui/issues/24547 -using NUnit.Framework; +using NUnit.Framework; using UITest.Appium; using UITest.Core; @@ -17,5 +16,4 @@ public void VerifyInitialToolbarButtonHidden() App.WaitForElement("DetailButton"); VerifyScreenshot(); } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28130_flyout.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28130_flyout.cs new file mode 100644 index 000000000000..8d9523d9c0e7 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28130_flyout.cs @@ -0,0 +1,27 @@ +#if WINDOWS +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues +{ + public class Issue28130_Flyout : _IssuesUITest + { + public Issue28130_Flyout(TestDevice testDevice) : base(testDevice) + { + } + + public override string Issue => "[Windows] Flyout Menu Icon disappears from Window Title Bar after Navigation"; + + [Test] + [Category(UITestCategories.FlyoutPage)] + public void FlyoutMenuShouldNotDisappearAfterNavigation_FlyoutPage() + { + App.WaitForElement("detailLabel"); + App.TapInFlyoutPageFlyout("NavigateButton"); + App.WaitForElement("newPageLabel"); + App.TapFlyoutPageIcon(); + } + } +} +#endif \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28130_shell.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28130_shell.cs new file mode 100644 index 000000000000..9e4fa7649c6d --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28130_shell.cs @@ -0,0 +1,28 @@ +#if WINDOWS +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues +{ + public class Issue28130_Shell : _IssuesUITest + { + public Issue28130_Shell(TestDevice testDevice) : base(testDevice) + { + } + + public override string Issue => "[Windows] Shell Flyout Menu Icon disappears from Window Title Bar after Navigation"; + + [Test] + [Category(UITestCategories.Shell)] + public void FlyoutMenuShouldNotDisappearAfterNavigation_Shell() + { + App.WaitForElement("detailLabel"); + App.TapShellFlyoutIcon(); + App.Tap("Navigate to Page 2"); + App.WaitForElement("newPageLabel"); + App.TapShellFlyoutIcon(); + } + } +} +#endif \ No newline at end of file From 559da79488fb1539820a7491fefe40c9de6f8b67 Mon Sep 17 00:00:00 2001 From: HarishwaranVijayakumar Date: Fri, 28 Mar 2025 21:30:35 +0530 Subject: [PATCH 09/18] [Android, iOS] Dynamically setting SearchHandler Query property does not update text in the search box (#28400) * Fix for query not updating during runtime * Fix for SearchHandler * Fix for SearchHandler Dynamic Update * Fix for Dynamic SearchHandler QueryProperty * Fix for query not updating in runtime * Fix for searchHandler * Updating the images * Revert "Updating the images" This reverts commit d3959fed89ca4e5268ab55be7254f67424656b32. * Modified Test case for search Handler * Modifying the testcase and. the code --- .../Android/SearchHandlerAppearanceTracker.cs | 22 +++++++ .../iOS/SearchHandlerAppearanceTracker.cs | 13 +++++ .../TestCases.HostApp/Issues/Issue14497.cs | 57 +++++++++++++++++++ .../Tests/Issues/Issue14497.cs | 23 ++++++++ .../src/UITest.Appium/HelperExtensions.cs | 33 +++++++++++ 5 files changed, 148 insertions(+) create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue14497.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue14497.cs diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/SearchHandlerAppearanceTracker.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/SearchHandlerAppearanceTracker.cs index 6b21640b0ca2..e09a56b39f98 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/SearchHandlerAppearanceTracker.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/SearchHandlerAppearanceTracker.cs @@ -82,6 +82,28 @@ protected virtual void SearchHandlerPropertyChanged(object sender, System.Compon { UpdateAutomationId(); } + else if (e.Is(SearchHandler.QueryProperty)) + { + UpdateText(); + } + } + + void UpdateText() + { + int cursorPosition = _editText.SelectionStart; + bool selectionExists = _editText.HasSelection; + + _editText.Text = _searchHandler.Query ?? string.Empty; + + UpdateTextTransform(); + + // If we had a selection, place the cursor at the end of text + // Otherwise try to maintain the cursor at its previous position + int textLength = _editText.Text?.Length ?? 0; + int newPosition = selectionExists ? textLength : Math.Min(cursorPosition, textLength); + + // Prevents the cursor from resetting to position zero when text is set programmatically + _editText.SetSelection(newPosition); } void EditTextFocusChange(object s, AView.FocusChangeEventArgs args) diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/SearchHandlerAppearanceTracker.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/SearchHandlerAppearanceTracker.cs index ecea8c950e70..7d8e11076e63 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/SearchHandlerAppearanceTracker.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/SearchHandlerAppearanceTracker.cs @@ -115,6 +115,19 @@ void SearchHandlerPropertyChanged(object sender, System.ComponentModel.PropertyC { UpdateSearchBarVerticalTextAlignment(_uiSearchBar.FindDescendantView()); } + else if (e.Is(SearchHandler.QueryProperty)) + { + UpdateText(_uiSearchBar.FindDescendantView()); + } + } + + void UpdateText(UITextField uiTextField) + { + if (uiTextField is null) + return; + + uiTextField.Text = _searchHandler.Query; + UpdateTextTransform(uiTextField); } void GetDefaultSearchBarColors(UISearchBar searchBar) diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue14497.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue14497.cs new file mode 100644 index 000000000000..85ef977a95a0 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue14497.cs @@ -0,0 +1,57 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 14497, "Dynamically setting SearchHandler Query property does not update text in the search box", PlatformAffected.Android | PlatformAffected.iOS)] +public class Issue14497 : Shell +{ + public Issue14497() + { + ShellContent shellContent = new ShellContent + { + Title = "Home", + ContentTemplate = new DataTemplate(typeof(Issue14497Page)), + Route = "MainPage" + }; + + Items.Add(shellContent); + } +} + +public class Issue14497Page : ContentPage +{ + Issue14497CustomSearchHandler _searchHandler; + public Issue14497Page() + { + _searchHandler = new Issue14497CustomSearchHandler(); + + Button button = new Button + { + Text = "Change Search Text", + AutomationId = "ChangeSearchText" + }; + + button.Clicked += (s,e) => _searchHandler.SetQuery("Hello World"); + + VerticalStackLayout stackLayout = new VerticalStackLayout + { + Children = { button } + }; + + Content = stackLayout; + Shell.SetSearchHandler(this, _searchHandler); + } +} + +public class Issue14497CustomSearchHandler : SearchHandler +{ + public Issue14497CustomSearchHandler() + { + Placeholder = "Search..."; + ShowsResults = false; + } + + public void SetQuery(string searchText) + { + Query = searchText; + } + +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue14497.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue14497.cs new file mode 100644 index 000000000000..825c93d804c0 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue14497.cs @@ -0,0 +1,23 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue14497 : _IssuesUITest +{ + public Issue14497(TestDevice device) : base(device) { } + + public override string Issue => "Dynamically setting SearchHandler Query property does not update text in the search box"; + const string ChangeSearchText = "ChangeSearchText"; + + [Test] + [Category(UITestCategories.Shell)] + public void DynamicallyQueryNotUpdating() + { + App.WaitForElement(ChangeSearchText); + App.Tap(ChangeSearchText); + var searchHandlerString = App.GetShellSearchHandler().GetText(); + Assert.That(searchHandlerString, Is.EqualTo("Hello World")); + } +} \ No newline at end of file diff --git a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs index 954e3c8dd2f0..576472c47b94 100644 --- a/src/TestUtils/src/UITest.Appium/HelperExtensions.cs +++ b/src/TestUtils/src/UITest.Appium/HelperExtensions.cs @@ -2466,5 +2466,38 @@ public static void SetTestConfigurationArg(this IConfig config, string key, stri startupArg.Add(key, value); config.SetProperty("TestConfigurationArgs", startupArg); } + + ///

+ /// Gets the search handler element for the shell. + /// This method is used to find the search handler element in the app. + /// It uses different queries based on the app type (Android, iOS, Catalyst, or Windows). + /// + /// The IApp instance representing the application. + /// The search handler element for the shell. + public static IUIElement GetShellSearchHandler(this IApp app) + { + IUIElement? element = null; + + if (app is AppiumAndroidApp) + { + element = app.WaitForElement(AppiumQuery.ByXPath("//android.widget.EditText")); + } + else if (app is AppiumIOSApp || app is AppiumCatalystApp) + { + element = app.WaitForElement(AppiumQuery.ByXPath("//XCUIElementTypeSearchField")); + } + else if (app is AppiumWindowsApp) + { + element = app.WaitForElement("TextBox"); + } + + // Ensure the element is not null before returning + if (element is null) + { + throw new InvalidOperationException("SearchHandler element not found."); + } + + return element; + } } } \ No newline at end of file From 589fd52f20fee4bfa50de1c4726ffe79feebfd8e Mon Sep 17 00:00:00 2001 From: Ahamed-Ali <102580874+Ahamed-Ali@users.noreply.github.com> Date: Sun, 30 Mar 2025 05:47:04 +0530 Subject: [PATCH 10/18] [Catalyst] Fixed the CanMixGroups Set to False Still Allows Reordering Between Groups in CollectionView (#28623) * Fixed the CanMixGroups Set to False Still Allows Reordering Between Groups in CollectionView * Committed the test case and sample * Committed the pending snap --- .../iOS/ReorderableItemsViewController.cs | 5 ++ .../iOS/ReorderableItemsViewController2.cs | 5 ++ ...sShouldNotOccurWhenCanMixGroupsIsFalse.png | Bin 0 -> 11022 bytes .../TestCases.HostApp/Issues/Issue28530.xaml | 33 +++++++++++ .../Issues/Issue28530.xaml.cs | 56 ++++++++++++++++++ ...sShouldNotOccurWhenCanMixGroupsIsFalse.png | Bin 0 -> 9907 bytes .../Tests/Issues/Issue28530.cs | 23 +++++++ ...sShouldNotOccurWhenCanMixGroupsIsFalse.png | Bin 0 -> 15363 bytes 8 files changed, 122 insertions(+) create mode 100644 src/Controls/tests/TestCases.Android.Tests/snapshots/android/ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse.png create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue28530.xaml create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue28530.xaml.cs create mode 100644 src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse.png create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28530.cs create mode 100644 src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse.png diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ReorderableItemsViewController.cs b/src/Controls/src/Core/Handlers/Items/iOS/ReorderableItemsViewController.cs index 8878d36194b6..63dc064fd595 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/ReorderableItemsViewController.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/ReorderableItemsViewController.cs @@ -21,6 +21,11 @@ public ReorderableItemsViewController(TItemsView reorderableItemsView, ItemsView // For some reason it only seemed to work when the CollectionView was inside the Flyout section of a FlyoutPage. // The UILongPressGestureRecognizer is simple enough to set up so let's just add our own. InstallsStandardGestureForInteractiveMovement = false; +#if MACCATALYST + // On Mac Catalyst, the default normal press and drag interactions occur, causing the CanMixGroups = false logic to not work. + // Since all reordering logic is handled exclusively by UILongPressGestureRecognizer, we can set DragInteractionEnabled to false, ensuring that only the long press gesture is used. + CollectionView.DragInteractionEnabled = false; +#endif } public override bool CanMoveItem(UICollectionView collectionView, NSIndexPath indexPath) diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/ReorderableItemsViewController2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/ReorderableItemsViewController2.cs index 12e33e65ef62..2114d4eecc35 100644 --- a/src/Controls/src/Core/Handlers/Items2/iOS/ReorderableItemsViewController2.cs +++ b/src/Controls/src/Core/Handlers/Items2/iOS/ReorderableItemsViewController2.cs @@ -21,6 +21,11 @@ public ReorderableItemsViewController2(TItemsView reorderableItemsView, UICollec // For some reason it only seemed to work when the CollectionView was inside the Flyout section of a FlyoutPage. // The UILongPressGestureRecognizer is simple enough to set up so let's just add our own. InstallsStandardGestureForInteractiveMovement = false; +#if MACCATALYST + // On Mac Catalyst, the default normal press and drag interactions occur, causing the CanMixGroups = false logic to not work. + // Since all reordering logic is handled exclusively by UILongPressGestureRecognizer, we can set DragInteractionEnabled to false, ensuring that only the long press gesture is used. + CollectionView.DragInteractionEnabled = false; +#endif } public override bool CanMoveItem(UICollectionView collectionView, NSIndexPath indexPath) diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse.png new file mode 100644 index 0000000000000000000000000000000000000000..f03b47c2e3a3d9cc7af5a65c760f7bcfc22ca990 GIT binary patch literal 11022 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 TX*vNmpbO`%&Y{1*`qTdbM(tLU literal 0 HcmV?d00001 diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue28530.xaml b/src/Controls/tests/TestCases.HostApp/Issues/Issue28530.xaml new file mode 100644 index 000000000000..3d4a843a08d6 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue28530.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue28530.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue28530.xaml.cs new file mode 100644 index 000000000000..84218d89b59f --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue28530.xaml.cs @@ -0,0 +1,56 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; + +namespace Maui.Controls.Sample.Issues; +[Issue(IssueTracker.Github, 28530, "[Catalyst] CanMixGroups Set to False Still Allows Reordering Between Groups in CollectionView", PlatformAffected.macOS)] +public partial class Issue28530 : ContentPage +{ + public Issue28530() + { + InitializeComponent(); + BindingContext = new Issue28530CollectionViewViewModel(); + } +} + +public class Issue28530CollectionViewViewModel : INotifyPropertyChanged +{ + public ObservableCollection GroupedItems { get; set; } + public event PropertyChangedEventHandler PropertyChanged; + + public Issue28530CollectionViewViewModel() + { + GroupedItems = new ObservableCollection + { + new Issue28530GroupedItem("Group 1") + { + new Issue28530Item { Name = "Item 1" }, + new Issue28530Item { Name = "Item 2" } + }, + new Issue28530GroupedItem("Group 2") + { + new Issue28530Item { Name = "Item 3" }, + new Issue28530Item { Name = "Item 4" } + } + }; + } + + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} + +public class Issue28530GroupedItem : ObservableCollection +{ + public string GroupHeaderName { get; set; } + + public Issue28530GroupedItem(string groupHeaderName) + { + GroupHeaderName = groupHeaderName; + } +} + +public class Issue28530Item +{ + public string Name { get; set; } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse.png new file mode 100644 index 0000000000000000000000000000000000000000..dc986a7d75c5402f444e140cda50a533ab7893b8 GIT binary patch literal 9907 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 "[Catalyst] CanMixGroups Set to False Still Allows Reordering Between Groups in CollectionView"; + + [Test] + [Category(UITestCategories.CollectionView)] + public void ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse() + { + App.WaitForElement("CollectionViewControl"); + App.DragAndDrop("Item 2", "Item 3"); + VerifyScreenshot(); + } +} +#endif \ No newline at end of file diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse.png new file mode 100644 index 0000000000000000000000000000000000000000..f64c10a261ffe6dd6a6c4c8f306e42b5d9c02551 GIT binary patch literal 15363 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 WvD+<3X{sp(K^^isSowAE5B~w^zNfVS literal 0 HcmV?d00001 From 712745486c93f59ae9fc4b9ca5040fabf30eaa47 Mon Sep 17 00:00:00 2001 From: Tamilarasan Paranthaman <93904422+Tamilarasan-Paranthaman@users.noreply.github.com> Date: Thu, 3 Apr 2025 20:34:01 +0530 Subject: [PATCH 11/18] [Android] Fix for SearchHandler Placeholder did not update when changed at runtime (#28636) * Placeholder issue fix * Added Android snapshot * Added Mac and Windows snapshots --- .../Android/SearchHandlerAppearanceTracker.cs | 9 ++++ .../VerifySearchHandlerPlaceholderText.png | Bin 0 -> 28452 bytes .../TestCases.HostApp/Issues/Issue28634.cs | 49 ++++++++++++++++++ .../VerifySearchHandlerPlaceholderText.png | Bin 0 -> 13054 bytes .../Tests/Issues/Issue28634.cs | 23 ++++++++ .../VerifySearchHandlerPlaceholderText.png | Bin 0 -> 10574 bytes .../VerifySearchHandlerPlaceholderText.png | Bin 0 -> 39194 bytes 7 files changed, 81 insertions(+) create mode 100644 src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifySearchHandlerPlaceholderText.png create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue28634.cs create mode 100644 src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/VerifySearchHandlerPlaceholderText.png create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28634.cs create mode 100644 src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySearchHandlerPlaceholderText.png create mode 100644 src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySearchHandlerPlaceholderText.png diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/SearchHandlerAppearanceTracker.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/SearchHandlerAppearanceTracker.cs index e09a56b39f98..52fadb15e8fe 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/SearchHandlerAppearanceTracker.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/SearchHandlerAppearanceTracker.cs @@ -54,6 +54,10 @@ protected virtual void SearchHandlerPropertyChanged(object sender, System.Compon { UpdateTextTransform(); } + else if (e.Is(SearchHandler.PlaceholderProperty)) + { + UpdatePlaceholder(); + } else if (e.Is(SearchHandler.PlaceholderColorProperty)) { UpdatePlaceholderColor(); @@ -137,6 +141,11 @@ void UpdateFont() _editText.SetTextSize(ComplexUnitType.Sp, (float)_searchHandler.FontSize); } + void UpdatePlaceholder() + { + _editText.Hint = _searchHandler.Placeholder; + } + void UpdatePlaceholderColor() { _editText.UpdatePlaceholderColor(_searchHandler.PlaceholderColor); diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifySearchHandlerPlaceholderText.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifySearchHandlerPlaceholderText.png new file mode 100644 index 0000000000000000000000000000000000000000..3065fce5870784f7fd95bb66f41b3190d36ff69d GIT binary patch literal 28452 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 CM5Sy1 literal 0 HcmV?d00001 diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue28634.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue28634.cs new file mode 100644 index 000000000000..3397fa26a9bf --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue28634.cs @@ -0,0 +1,49 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 28634, "[Android] SearchHandler Placeholder Text", PlatformAffected.Android)] +public class Issue28634 : TestShell +{ + + protected override void Init() + { + this.FlyoutBehavior = FlyoutBehavior.Flyout; + + var shellContent = new ShellContent + { + Title = "Home", + Route = "MainPage", + Content = new Issue28634ContentPage() { Title = "Home" } + }; + + Items.Add(shellContent); + + } + class Issue28634ContentPage : ContentPage + { + public Issue28634ContentPage() + { + + var searchHandler = new SearchHandler + { + Placeholder = "Type a fruit name to search", + PlaceholderColor = Colors.Red, + }; + + var button = new Button + { + Text = "Change SearchHandler Placeholder", + AutomationId = "button", + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + }; + button.Clicked += (s, e) => + { + searchHandler.Placeholder = "Type a vegetable name to search"; + }; + + Shell.SetSearchHandler(this, searchHandler); + + Content = button; + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/VerifySearchHandlerPlaceholderText.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/VerifySearchHandlerPlaceholderText.png new file mode 100644 index 0000000000000000000000000000000000000000..b148ed2453473758e1f4d0679c4ca8667771a2ab GIT binary patch literal 13054 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 KO!z6_*Z%?tTKu*E literal 0 HcmV?d00001 diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28634.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28634.cs new file mode 100644 index 000000000000..ed13a444331f --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue28634.cs @@ -0,0 +1,23 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue28634 : _IssuesUITest +{ + public override string Issue => "[Android] SearchHandler Placeholder Text"; + + public Issue28634(TestDevice device) + : base(device) + { } + + [Test] + [Category(UITestCategories.Shell)] + public void VerifySearchHandlerPlaceholderText() + { + App.WaitForElement("button"); + App.Tap("button"); + VerifyScreenshot(); + } +} diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySearchHandlerPlaceholderText.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySearchHandlerPlaceholderText.png new file mode 100644 index 0000000000000000000000000000000000000000..8a28a5837faadd66d3eb908811759363f67f3e8f GIT binary patch literal 10574 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 Date: Thu, 3 Apr 2025 20:51:44 +0530 Subject: [PATCH 12/18] [Testing] Migration of Compatibility.Core platform-specific unit tests into device tests - 8 (#28496) * code changes added * updated code --- .../Elements/Editor/EditorTests.Windows.cs | 37 ++++++++++++++++ .../Elements/Editor/EditorTests.iOS.cs | 39 +++++++++++++++-- .../Elements/Entry/EntryTests.Windows.cs | 35 ++++++++++++++++ .../Elements/Entry/EntryTests.iOS.cs | 42 ++++++++++++++++--- .../NavigationPage/NavigationPageTests.iOS.cs | 26 ++++++++++++ .../Elements/Page/PageTests.Android.cs | 37 ++++++++++++++++ .../Elements/Page/PageTests.iOS.cs | 21 ++++++++++ .../ImageButtonHandlerTests.iOS.cs | 17 ++++++++ 8 files changed, 246 insertions(+), 8 deletions(-) create mode 100644 src/Controls/tests/DeviceTests/Elements/Page/PageTests.Android.cs diff --git a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Windows.cs index e9bb56dc2acf..6dc9ab2f9d7c 100644 --- a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.Windows.cs @@ -1,7 +1,14 @@ #nullable enable +using Xunit; +using System; +using System.ComponentModel; using System.Threading.Tasks; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Dispatching; using Microsoft.Maui.Handlers; using Microsoft.UI.Xaml.Controls; +using WFlowDirection = Microsoft.UI.Xaml.FlowDirection; +using WTextAlignment = Microsoft.UI.Xaml.TextAlignment; namespace Microsoft.Maui.DeviceTests { @@ -41,5 +48,35 @@ Task GetPlatformIsVisible(EditorHandler editorHandler) return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; }); } + //src/Compatibility/Core/tests/WinUI/FlowDirectionTests.cs + [Theory] + [InlineData(true, FlowDirection.RightToLeft, WTextAlignment.Left, WFlowDirection.RightToLeft)] + [InlineData(true, FlowDirection.LeftToRight, WTextAlignment.Left, WFlowDirection.LeftToRight)] + [InlineData(false, FlowDirection.LeftToRight, WTextAlignment.Left, WFlowDirection.LeftToRight)] + [Description("The Editor's text alignment and flow direction should match the expected values when FlowDirection is applied explicitly or implicitly.")] + public async Task EditorAlignmentMatchesFlowDirection(bool isExplicit, FlowDirection flowDirection, WTextAlignment expectedAlignment, WFlowDirection expectedFlowDirection) + { + var editor = new Editor { Text = " تسجيل الدخول" }; + var contentPage = new ContentPage { Title = "Flow Direction", Content = editor }; + + if (isExplicit) + { + editor.FlowDirection = flowDirection; + } + else + { + contentPage.FlowDirection = flowDirection; + } + + var handler = await CreateHandlerAsync(editor); + var (nativeAlignment, nativeFlowDirection) = await contentPage.Dispatcher.DispatchAsync(() => + { + var textField = GetPlatformControl(handler); + return (textField.TextAlignment, textField.FlowDirection); + }); + + Assert.Equal(expectedAlignment, nativeAlignment); + Assert.Equal(expectedFlowDirection, nativeFlowDirection); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.iOS.cs index 6be8fb8fb7b4..6f89153da58c 100644 --- a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.iOS.cs @@ -1,11 +1,14 @@ -using System.Linq; +using UIKit; +using Xunit; +using System.Linq; using System.Threading.Tasks; using Microsoft.Maui.Controls; +using Microsoft.Maui.Dispatching; using Microsoft.Maui.Handlers; using Microsoft.Maui.Hosting; using Microsoft.Maui.Platform; -using Xunit; using static Microsoft.Maui.DeviceTests.AssertHelpers; +using System.ComponentModel; namespace Microsoft.Maui.DeviceTests { @@ -48,7 +51,7 @@ Task GetPlatformOpacity(EditorHandler editorHandler) return InvokeOnMainThreadAsync(() => { var nativeView = GetPlatformControl(editorHandler); - return (float)nativeView.Alpha; + return (float)nativeView.Alpha; }); } @@ -103,5 +106,35 @@ await CreateHandlerAndAddToWindow(contentPage, async () => }); } } + + //src/Compatibility/Core/tests/iOS/FlowDirectionTests.cs + [Theory] + [InlineData(true, FlowDirection.LeftToRight, UITextAlignment.Left)] + [InlineData(true, FlowDirection.RightToLeft, UITextAlignment.Right)] + [InlineData(false, FlowDirection.LeftToRight, UITextAlignment.Left)] + [Description("The Editor's text alignment should match the expected alignment when FlowDirection is applied explicitly or implicitly")] + public async Task EditorAlignmentMatchesFlowDirection(bool isExplicit, FlowDirection flowDirection, UITextAlignment expectedAlignment) + { + var editor = new Editor { Text = "Checking flow direction" }; + var contentPage = new ContentPage { Title = "Flow Direction", Content = editor }; + + if (isExplicit) + { + editor.FlowDirection = flowDirection; + } + else + { + contentPage.FlowDirection = flowDirection; + } + + var handler = await CreateHandlerAsync(editor); + var nativeAlignment = await contentPage.Dispatcher.DispatchAsync(() => + { + var textField = GetPlatformControl(handler); + return textField.TextAlignment; + }); + + Assert.Equal(expectedAlignment, nativeAlignment); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Windows.cs b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Windows.cs index f76b6526f23b..a41c816bea69 100644 --- a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Windows.cs +++ b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.Windows.cs @@ -1,7 +1,12 @@ #nullable enable +using Xunit; +using System.ComponentModel; using System.Threading.Tasks; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Dispatching; using Microsoft.Maui.Handlers; using Microsoft.UI.Xaml.Controls; +using WTextAlignment = Microsoft.UI.Xaml.TextAlignment; namespace Microsoft.Maui.DeviceTests { @@ -41,5 +46,35 @@ Task GetPlatformIsVisible(EntryHandler entryHandler) return nativeView.Visibility == Microsoft.UI.Xaml.Visibility.Visible; }); } + //src/Compatibility/Core/tests/WinUI/FlowDirectionTests.cs + [Theory] + [InlineData(true, FlowDirection.LeftToRight, WTextAlignment.Left)] + [InlineData(true, FlowDirection.RightToLeft, WTextAlignment.Left)] + [InlineData(false, FlowDirection.LeftToRight, WTextAlignment.Left)] + [InlineData(false, FlowDirection.RightToLeft, WTextAlignment.Left)] + [Description("The Entry's text alignment should match the expected alignment when FlowDirection is applied explicitly or implicitly.")] + public async Task EntryAlignmentMatchesFlowDirection(bool isExplicit, FlowDirection flowDirection, WTextAlignment expectedAlignment) + { + var entry = new Entry { Text = "Checking flow direction", HorizontalTextAlignment = TextAlignment.Start }; + var contentPage = new ContentPage { Title = "Flow Direction", Content = entry }; + + if (isExplicit) + { + entry.FlowDirection = flowDirection; + } + else + { + contentPage.FlowDirection = flowDirection; + } + + var handler = await CreateHandlerAsync(entry); + var nativeAlignment = await contentPage.Dispatcher.DispatchAsync(() => + { + var textField = GetPlatformControl(handler); + return textField.TextAlignment; + }); + + Assert.Equal(expectedAlignment, nativeAlignment); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.iOS.cs index 4a2e9e48c957..282b8a5647ef 100644 --- a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.iOS.cs @@ -1,13 +1,15 @@ -using System.Linq; +using UIKit; +using Xunit; +using System.ComponentModel; +using System.Linq; using System.Threading.Tasks; using Microsoft.Maui.Controls; using Microsoft.Maui.Controls.Handlers.Compatibility; using Microsoft.Maui.DeviceTests.TestCases; +using Microsoft.Maui.Dispatching; using Microsoft.Maui.Handlers; using Microsoft.Maui.Hosting; using Microsoft.Maui.Platform; -using UIKit; -using Xunit; using static Microsoft.Maui.DeviceTests.AssertHelpers; namespace Microsoft.Maui.DeviceTests @@ -44,13 +46,13 @@ static int GetPlatformSelectionLength(EntryHandler entryHandler) return -1; } - + Task GetPlatformOpacity(EntryHandler entryHandler) { return InvokeOnMainThreadAsync(() => { var nativeView = GetPlatformControl(entryHandler); - return (float)nativeView.Alpha; + return (float)nativeView.Alpha; }); } @@ -332,5 +334,35 @@ await CreateHandlerAndAddToWindow(contentPage, async () => }); } } + + //src/Compatibility/Core/tests/iOS/FlowDirectionTests.cs + [Theory] + [InlineData(true, FlowDirection.LeftToRight, UITextAlignment.Left)] + [InlineData(true, FlowDirection.RightToLeft, UITextAlignment.Right)] + [InlineData(false, FlowDirection.LeftToRight, UITextAlignment.Left)] + [Description("The Entry's text alignment should match the expected alignment when FlowDirection is applied explicitly or implicitly")] + public async Task EntryAlignmentMatchesFlowDirection(bool isExplicit, FlowDirection flowDirection, UITextAlignment expectedAlignment) + { + var entry = new Entry { Text = "Checking flow direction", HorizontalTextAlignment = TextAlignment.Start }; + var contentPage = new ContentPage { Title = "Flow Direction", Content = entry }; + + if (isExplicit) + { + entry.FlowDirection = flowDirection; + } + else + { + contentPage.FlowDirection = flowDirection; + } + + var handler = await CreateHandlerAsync(entry); + var nativeAlignment = await contentPage.Dispatcher.DispatchAsync(() => + { + var textField = GetPlatformControl(handler); + return textField.TextAlignment; + }); + + Assert.Equal(expectedAlignment, nativeAlignment); + } } } \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/NavigationPage/NavigationPageTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/NavigationPage/NavigationPageTests.iOS.cs index eca6b8284954..89cd1b7ed98b 100644 --- a/src/Controls/tests/DeviceTests/Elements/NavigationPage/NavigationPageTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/NavigationPage/NavigationPageTests.iOS.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -7,6 +8,7 @@ using Microsoft.Maui.Controls.Handlers.Compatibility; using Microsoft.Maui.Controls.Platform; using Microsoft.Maui.DeviceTests.Stubs; +using Microsoft.Maui.Dispatching; using Microsoft.Maui.Handlers; using Microsoft.Maui.Hosting; using Microsoft.Maui.Platform; @@ -65,5 +67,29 @@ public async Task TranslucentNavigationBar(bool enabled) var translucent = await GetValueAsync(navPage, (handler) => (handler.ViewController as UINavigationController).NavigationBar.Translucent); Assert.Equal(enabled, translucent); } + + //src/Compatibility/Core/tests/iOS/NavigationTests.cs + [Fact] + [Description("Multiple calls to NavigationRenderer.Dispose shouldn't crash")] + public async Task NavigationRendererDoubleDisposal() + { + SetupBuilder(); + + var root = new ContentPage() + { + Title = "root", + Content = new Label { Text = "Hello" } + }; + + await root.Dispatcher.DispatchAsync(() => + { + var navPage = new NavigationPage(root); + var handler = CreateHandler(navPage); + + // Calling Dispose more than once should be fine + (handler as NavigationRenderer).Dispose(); + (handler as NavigationRenderer).Dispose(); + }); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Page/PageTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Page/PageTests.Android.cs new file mode 100644 index 000000000000..8ee2bf4a9524 --- /dev/null +++ b/src/Controls/tests/DeviceTests/Elements/Page/PageTests.Android.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Dispatching; +using Microsoft.Maui.Handlers; +using Microsoft.Maui.Platform; +using Xunit; +using AView = Android.Views.View; +using AndroidX.Fragment.App; + +namespace Microsoft.Maui.DeviceTests +{ + public partial class PageTests : ControlsHandlerTestBase + { + //src/Compatibility/Core/tests/Android/EmbeddingTests.cs + [Fact(DisplayName = "Can Create Platform View From ContentPage")] + public async Task CanCreatePlatformViewFromContentPage() + { + + var contentPage = new ContentPage { Title = "Embedded Page" }; + var handler = CreateHandler(contentPage); + var mauiContext = handler.MauiContext; + + await contentPage.Dispatcher.DispatchAsync(() => + { + + AView platformView = contentPage.ToPlatform(mauiContext); + if (platformView is FragmentContainerView containerView) + { + var activity = mauiContext.Context as AndroidX.Fragment.App.FragmentActivity; + var fragmentManager = activity.SupportFragmentManager; + var fragment = fragmentManager.FindFragmentById(containerView.Id); + Assert.NotNull(fragment); + } + }); + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/DeviceTests/Elements/Page/PageTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Page/PageTests.iOS.cs index 1c71d20140cb..045e31fb2c72 100644 --- a/src/Controls/tests/DeviceTests/Elements/Page/PageTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/Page/PageTests.iOS.cs @@ -3,9 +3,12 @@ using Microsoft.Maui.Controls; using Microsoft.Maui.Controls.PlatformConfiguration; using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific; +using Microsoft.Maui.Dispatching; +using Microsoft.Maui.Platform; using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; using Microsoft.Maui.Hosting; +using UIKit; using Xunit; namespace Microsoft.Maui.DeviceTests @@ -40,5 +43,23 @@ await CreateHandlerAndAddToWindow(shell, (handler) => } }); } + + //src/Compatibility/Core/tests/iOS/EmbeddingTests.cs + [Fact(DisplayName = "Can Create Platform View From ContentPage")] + public async Task CanCreateViewControllerFromContentPage() + { + var contentPage = new ContentPage { Title = "Embedded Page" }; + await contentPage.Dispatcher.DispatchAsync(async () => + { + var handler = CreateHandler(contentPage); + var mauiContext = handler.MauiContext; + + await contentPage.Dispatcher.DispatchAsync(() => + { + UIViewController viewController = contentPage.ToUIViewController(mauiContext); + Assert.NotNull(viewController); + }); + }); + } } } diff --git a/src/Core/tests/DeviceTests/Handlers/ImageButton/ImageButtonHandlerTests.iOS.cs b/src/Core/tests/DeviceTests/Handlers/ImageButton/ImageButtonHandlerTests.iOS.cs index ed1c2a690008..3817c8c1d01b 100644 --- a/src/Core/tests/DeviceTests/Handlers/ImageButton/ImageButtonHandlerTests.iOS.cs +++ b/src/Core/tests/DeviceTests/Handlers/ImageButton/ImageButtonHandlerTests.iOS.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using CoreGraphics; +using Microsoft.Maui.Controls; using Microsoft.Maui.DeviceTests.Stubs; using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; @@ -40,6 +41,22 @@ public async Task StrokeColorInitializesCorrectly() Assert.Equal(expectedValue, values.PlatformViewValue); } + //src/Compatibility/Core/tests/iOS/ImageButtonTests.cs + [Fact] + [Trait("Category", "ImageButton")] + public async Task CreatedWithCorrectButtonType() + { + var imageButton = new ImageButton(); + var handler = await CreateHandlerAsync(imageButton); + + var buttonType = await InvokeOnMainThreadAsync(() => + { + var uiButton = GetPlatformImageButton(handler); + return uiButton.ButtonType; + }); + + Assert.NotEqual(UIButtonType.Custom, buttonType); + } UIButton GetPlatformImageButton(ImageButtonHandler imageButtonHandler) => imageButtonHandler.PlatformView; From 2266e0d612a3d55827a0706c8b72c401a8ff68c0 Mon Sep 17 00:00:00 2001 From: Vignesh-SF3580 <102575140+Vignesh-SF3580@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:01:59 +0530 Subject: [PATCH 13/18] Fixed Test case failure in PR 28486 (#28780) * Resolved the ScrollView TestFailures in Inflight. * updated ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse ios image * Updated EntryClearButtonColorShouldMatchTextColor mac image --- ...sShouldNotOccurWhenCanMixGroupsIsFalse.png | Bin 15363 -> 30956 bytes .../ScrollView/ScrollViewHandler.iOS.cs | 9 +-------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ReorderBetweenGroupsShouldNotOccurWhenCanMixGroupsIsFalse.png index f64c10a261ffe6dd6a6c4c8f306e42b5d9c02551..8d1ec88316fec510d8d56d447ed7100483a95a3a 100644 GIT binary patch literal 30956 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 literal 15363 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 WvD+<3X{sp(K^^isSowAE5B~w^zNfVS diff --git a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs index c8e7d6620fba..36debcf218b8 100644 --- a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs +++ b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs @@ -83,14 +83,7 @@ public static void MapOrientation(IScrollViewHandler handler, IScrollView scroll return; } - if (scrollView.Orientation == ScrollOrientation.Neither) - { - platformView.ScrollEnabled = false; - } - else - { - platformView.ScrollEnabled = true; - } + platformView.UpdateIsEnabled(scrollView); platformView.InvalidateMeasure(scrollView); } From 62b699e8a3175138994c33ddb19d092e7ee8ad75 Mon Sep 17 00:00:00 2001 From: SuthiYuvaraj <92777079+SuthiYuvaraj@users.noreply.github.com> Date: Wed, 9 Apr 2025 01:14:29 +0530 Subject: [PATCH 14/18] Revert "Fix HideSoftInputOnTapped Not Working Net9 (#28534)" (#28802) This reverts commit f3ec03b253f0b5cd8428bfa7e84bcb23ed2eb483. --- ...SoftInputOnTappedChangedManager.Android.cs | 2 +- ...oftInputOnTappedChangedManager.Platform.cs | 2 +- .../HideSoftInputOnTappedChangedManager.cs | 2 +- .../TestCases.HostApp/Issues/Issue26792.cs | 40 ------------------- .../Tests/Issues/Issue26792.cs | 29 -------------- 5 files changed, 3 insertions(+), 72 deletions(-) delete mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue26792.cs delete mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26792.cs diff --git a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Android.cs b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Android.cs index 3501898dcfcf..3dc75e168a60 100644 --- a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Android.cs +++ b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Android.cs @@ -20,7 +20,7 @@ void OnWindowDispatchedTouch(object? sender, MotionEvent? e) foreach (var page in _contentPages) { - if ((page.HasNavigatedTo || page.Parent is Window) && + if (page.HasNavigatedTo && page.HideSoftInputOnTapped && page.Handler is IPlatformViewHandler pvh && pvh.MauiContext?.Context is not null) diff --git a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Platform.cs b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Platform.cs index 45b49052c65b..b5ed8c838a64 100644 --- a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Platform.cs +++ b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.Platform.cs @@ -14,7 +14,7 @@ partial class HideSoftInputOnTappedChangedManager internal void UpdatePage(ContentPage page) { - if (page.HideSoftInputOnTapped && (page.HasNavigatedTo || page.Parent is Window)) + if (page.HideSoftInputOnTapped && page.HasNavigatedTo) { if (!_contentPages.Contains(page)) { diff --git a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.cs b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.cs index 17f1c41e75d7..1f369165f5f8 100644 --- a/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.cs +++ b/src/Controls/src/Core/ContentPage/HideSoftInputOnTappedChanged/HideSoftInputOnTappedChangedManager.cs @@ -13,7 +13,7 @@ bool FeatureEnabled { foreach (var page in _contentPages) { - if (page.HideSoftInputOnTapped && (page.HasNavigatedTo || page.Parent is Window)) + if (page.HideSoftInputOnTapped && page.HasNavigatedTo) return true; } return false; diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue26792.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue26792.cs deleted file mode 100644 index cb4045de9468..000000000000 --- a/src/Controls/tests/TestCases.HostApp/Issues/Issue26792.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Maui.Controls.Sample.Issues; - -[Issue(IssueTracker.Github, 26792, "HideSoftInputOnTapped Not Working", PlatformAffected.Android)] -public class Issue26792 : ContentPage -{ - - VerticalStackLayout stackLayout; - Button _button; - - Entry _entry; - public Issue26792() - { - this.HideSoftInputOnTapped = true; - stackLayout = new VerticalStackLayout - { - HorizontalOptions = LayoutOptions.Center, - VerticalOptions = LayoutOptions.Center, - AutomationId = "Issue26792StackLayout", - }; - _button = new Button - { - Text = "Click Me", - AutomationId = "Issue26792Button", - HorizontalOptions = LayoutOptions.Center, - VerticalOptions = LayoutOptions.Center, - }; - _entry = new Entry - { - Placeholder = "Enter text", - AutomationId = "Issue26792Entry", - HorizontalOptions = LayoutOptions.Center, - VerticalOptions = LayoutOptions.Center, - }; - - stackLayout.Children.Add(_button); - stackLayout.Children.Add(_entry); - Content = stackLayout; - } -} - diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26792.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26792.cs deleted file mode 100644 index 88bd8968731c..000000000000 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26792.cs +++ /dev/null @@ -1,29 +0,0 @@ -#if TEST_FAILS_ON_CATALYST && TEST_FAILS_ON_WINDOWS //As SoftKeyboard is not supported -using NUnit.Framework; -using UITest.Appium; -using UITest.Core; - -namespace Microsoft.Maui.TestCases.Tests.Issues; - -public class Issue26792 : _IssuesUITest -{ - public Issue26792(TestDevice testDevice) : base(testDevice) - { - } - - public override string Issue => "HideSoftInputOnTapped Not Working"; - - [Test] - [Category(UITestCategories.Page)] - public void SoftInputShouldHiddenOnTap() - { - App.WaitForElement("Issue26792Entry"); - App.Tap("Issue26792Entry"); - App.WaitForElement("Issue26792Button"); - App.Tap("Issue26792Button"); - App.WaitForElement("Issue26792Entry"); - Assert.That(App.IsKeyboardShown(), Is.False); - - } -} -#endif From 7e09fc725455871cb1c8b304bf9aa3b70da5db81 Mon Sep 17 00:00:00 2001 From: Vignesh-SF3580 <102575140+Vignesh-SF3580@users.noreply.github.com> Date: Thu, 10 Apr 2025 19:43:04 +0530 Subject: [PATCH 15/18] Fixed testcase failures in PR28867. (#28889) --- .../src/Handlers/ScrollView/ScrollViewHandler.Windows.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.Windows.cs b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.Windows.cs index 1a6752f2e4a0..1bfe2081418e 100644 --- a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.Windows.cs +++ b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.Windows.cs @@ -67,7 +67,11 @@ public static void MapVerticalScrollBarVisibility(IScrollViewHandler handler, IS public static void MapOrientation(IScrollViewHandler handler, IScrollView scrollView) { - handler.PlatformView?.UpdateScrollBarVisibility(scrollView.Orientation, scrollView.HorizontalScrollBarVisibility); + var scrollBarVisibility = scrollView.Orientation == ScrollOrientation.Horizontal + ? scrollView.HorizontalScrollBarVisibility + : scrollView.VerticalScrollBarVisibility; + + handler.PlatformView?.UpdateScrollBarVisibility(scrollView.Orientation, scrollBarVisibility); } public static void MapRequestScrollTo(IScrollViewHandler handler, IScrollView scrollView, object? args) From 767307c592550e4699c9fb94c46a18277d7734ab Mon Sep 17 00:00:00 2001 From: LogishaSelvarajSF4525 Date: Mon, 14 Apr 2025 18:59:01 +0530 Subject: [PATCH 16/18] [Testing] Feature Matrix UITest Cases for CollectionView Header/Footer Feature (#28938) * Added feature tests for Header/Footer * added test cases * update the test cases * update the changes * modified * updated the test cases * update the changes * update the changes * updated the UI and Test cases * Updated test cases * add fails attributes * updated * modified changes * updated changes * renamed --- .../CoreViews/CorePageView.cs | 1 + .../CollectionViewControlPage.xaml | 28 + .../CollectionViewControlPage.xaml.cs | 26 + .../CollectionViewOptionsPage.xaml | 275 +++ .../CollectionViewOptionsPage.xaml.cs | 278 +++ .../CollectionView/CollectionViewViewModel.cs | 293 +++ .../CollectionView/HeaderFooterMainPage.xaml | 15 + .../HeaderFooterMainPage.xaml.cs | 26 + ...CollectionView_HeaderFooterFeatureTests.cs | 1762 +++++++++++++++++ 9 files changed, 2704 insertions(+) create mode 100644 src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewControlPage.xaml create mode 100644 src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewControlPage.xaml.cs create mode 100644 src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewOptionsPage.xaml create mode 100644 src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewOptionsPage.xaml.cs create mode 100644 src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewViewModel.cs create mode 100644 src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/HeaderFooterMainPage.xaml create mode 100644 src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/HeaderFooterMainPage.xaml.cs create mode 100644 src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_HeaderFooterFeatureTests.cs diff --git a/src/Controls/tests/TestCases.HostApp/CoreViews/CorePageView.cs b/src/Controls/tests/TestCases.HostApp/CoreViews/CorePageView.cs index 9f2fc6c54f1a..61d2bfa29c0c 100644 --- a/src/Controls/tests/TestCases.HostApp/CoreViews/CorePageView.cs +++ b/src/Controls/tests/TestCases.HostApp/CoreViews/CorePageView.cs @@ -77,6 +77,7 @@ public override string ToString() new GalleryPageFactory(() => new TimePickerCoreGalleryPage(), "Time Picker Gallery"), new GalleryPageFactory(() => new WebViewCoreGalleryPage(), "WebView Gallery"), new GalleryPageFactory(() => new SliderControlPage(), "Slider Feature Matrix"), + new GalleryPageFactory(() => new HeaderFooterMainPage(), "CollectionView Feature Matrix"), }; public CorePageView(Page rootPage) diff --git a/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewControlPage.xaml b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewControlPage.xaml new file mode 100644 index 000000000000..5c49a597e156 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewControlPage.xaml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewControlPage.xaml.cs b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewControlPage.xaml.cs new file mode 100644 index 000000000000..27e08b4408c5 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewControlPage.xaml.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.Maui.Controls; +using System.Collections.ObjectModel; + +namespace Maui.Controls.Sample +{ + public partial class CollectionViewControlPage : ContentPage + { + private CollectionViewViewModel _viewModel; + + public CollectionViewControlPage() + { + InitializeComponent(); + _viewModel = new CollectionViewViewModel(); + _viewModel.ItemsSourceType = ItemsSourceType.ObservableCollection5T; + BindingContext = _viewModel; + } + + private async void NavigateToOptionsPage_Clicked(object sender, EventArgs e) + { + BindingContext = _viewModel = new CollectionViewViewModel(); + _viewModel.ItemsSourceType = ItemsSourceType.ObservableCollection5T; + await Navigation.PushAsync(new CollectionViewOptionsPage(_viewModel)); + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewOptionsPage.xaml b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewOptionsPage.xaml new file mode 100644 index 000000000000..7460999f5a1e --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewOptionsPage.xaml @@ -0,0 +1,275 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewOptionsPage.xaml.cs b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewOptionsPage.xaml.cs new file mode 100644 index 000000000000..ccf26c04cc54 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewOptionsPage.xaml.cs @@ -0,0 +1,278 @@ +using System; +using Microsoft.Maui.Controls; +using System.Collections.ObjectModel; +using Maui.Controls.Sample.CollectionViewGalleries; + +namespace Maui.Controls.Sample +{ + public partial class CollectionViewOptionsPage : ContentPage + { + private CollectionViewViewModel _viewModel; + + public CollectionViewOptionsPage(CollectionViewViewModel viewModel) + { + InitializeComponent(); + _viewModel = viewModel; + BindingContext = _viewModel; + } + + private void ApplyButton_Clicked(object sender, EventArgs e) + { + Navigation.PopAsync(); + } + + private void OnEmptyViewChanged(object sender, CheckedChangedEventArgs e) + { + if (EmptyViewNone.IsChecked) + { + _viewModel.EmptyView = null; + } + else if (EmptyViewString.IsChecked) + { + _viewModel.EmptyView = "No Items Available(String)"; + } + } + + private void OnHeaderChanged(object sender, CheckedChangedEventArgs e) + { + if (HeaderNone.IsChecked) + { + _viewModel.Header = null; + } + else if (HeaderString.IsChecked) + { + _viewModel.Header = "CollectionView Header(String)"; + } + else if (HeaderGrid.IsChecked) + { + Grid grid = new Grid + { + BackgroundColor = Colors.LightGray, + Padding = new Thickness(10) + }; + grid.Children.Add(new Label + { + Text = "CollectionView Header(Grid View)", + FontSize = 18, + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + TextColor = Colors.Blue, + AutomationId = "HeaderViewLabel" + }); + _viewModel.Header = grid; + } + } + + private void OnFooterChanged(object sender, CheckedChangedEventArgs e) + { + if (FooterNone.IsChecked) + { + _viewModel.Footer = null; + } + else if (FooterString.IsChecked) + { + _viewModel.Footer = "CollectionView Footer(String)"; + } + else if (FooterGrid.IsChecked) + { + Grid grid = new Grid + { + BackgroundColor = Colors.LightGray, + Padding = new Thickness(10) + }; + grid.Children.Add(new Label + { + Text = "CollectionView Footer(Grid View)", + FontSize = 18, + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + TextColor = Colors.Red, + AutomationId = "FooterViewLabel" + }); + _viewModel.Footer = grid; + } + } + + private void OnHeaderTemplateChanged(object sender, CheckedChangedEventArgs e) + { + if (HeaderTemplateNone.IsChecked) + { + _viewModel.HeaderTemplate = null; + } + else if (HeaderTemplateGrid.IsChecked) + { + _viewModel.HeaderTemplate = new DataTemplate(() => + { + Grid grid = new Grid + { + BackgroundColor = Colors.LightGray, + Padding = new Thickness(10) + }; + grid.Children.Add(new Label + { + Text = "Header Template(Grid View)", + FontSize = 18, + FontAttributes = FontAttributes.Bold, + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + TextColor = Colors.Blue, + AutomationId = "HeaderTemplateLabel" + }); + return grid; + }); + } + } + + private void OnFooterTemplateChanged(object sender, CheckedChangedEventArgs e) + { + if (FooterTemplateNone.IsChecked) + { + _viewModel.FooterTemplate = null; + } + else if (FooterTemplateGrid.IsChecked) + { + _viewModel.FooterTemplate = new DataTemplate(() => + { + Grid grid = new Grid + { + BackgroundColor = Colors.LightGray, + Padding = new Thickness(10) + }; + grid.Children.Add(new Label + { + Text = "Footer Template(Grid View)", + FontSize = 18, + FontAttributes = FontAttributes.Bold, + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + TextColor = Colors.Green, + AutomationId = "FooterTemplateLabel" + }); + return grid; + }); + } + } + + private void OnGroupHeaderTemplateChanged(object sender, CheckedChangedEventArgs e) + { + if (GroupHeaderTemplateNone.IsChecked) + { + _viewModel.GroupHeaderTemplate = null; + } + else if (GroupHeaderTemplateGrid.IsChecked) + { + _viewModel.GroupHeaderTemplate = new DataTemplate(() => + { + Grid grid = new Grid + { + BackgroundColor = Colors.LightGray, + Padding = new Thickness(10) + }; + grid.Children.Add(new Label + { + Text = "Group Header Template(Grid View)", + FontSize = 18, + FontAttributes = FontAttributes.Bold, + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + TextColor = Colors.Green, + AutomationId = "GroupHeaderTemplateLabel" + }); + return grid; + }); + } + } + private void OnGroupFooterTemplateChanged(object sender, CheckedChangedEventArgs e) + { + if (GroupFooterTemplateNone.IsChecked) + { + _viewModel.GroupFooterTemplate = null; + } + else if (GroupFooterTemplateGrid.IsChecked) + { + _viewModel.GroupFooterTemplate = new DataTemplate(() => + { + Grid grid = new Grid + { + BackgroundColor = Colors.LightGray, + Padding = new Thickness(10) + }; + grid.Children.Add(new Label + { + Text = "Group Footer Template(Grid View)", + FontSize = 18, + FontAttributes = FontAttributes.Bold, + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center, + TextColor = Colors.Red, + AutomationId = "GroupFooterTemplateLabel" + }); + return grid; + }); + } + } + private void OnIsGroupedChanged(object sender, CheckedChangedEventArgs e) + { + if (IsGroupedFalse.IsChecked) + { + _viewModel.IsGrouped = false; + } + else if (IsGroupedTrue.IsChecked) + { + _viewModel.IsGrouped = true; + } + } + + private void OnItemTemplateChanged(object sender, CheckedChangedEventArgs e) + { + if (ItemTemplateNone.IsChecked) + { + _viewModel.ItemTemplate = null; + } + else if (ItemTemplateBasic.IsChecked) + { + _viewModel.ItemTemplate = new DataTemplate(() => + { + var label = new Label(); + label.SetBinding(Label.TextProperty, new Binding("Caption")); + + return label; + }); + } + } + + private void OnItemsLayoutChanged(object sender, CheckedChangedEventArgs e) + { + if (ItemsLayoutVerticalList.IsChecked) + { + _viewModel.ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical); + } + else if (ItemsLayoutHorizontalList.IsChecked) + { + _viewModel.ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Horizontal); + } + else if (ItemsLayoutVerticalGrid.IsChecked) + { + _viewModel.ItemsLayout = new GridItemsLayout(2, ItemsLayoutOrientation.Vertical); // 2 columns + } + else if (ItemsLayoutHorizontalGrid.IsChecked) + { + _viewModel.ItemsLayout = new GridItemsLayout(2, ItemsLayoutOrientation.Horizontal); // 2 rows + } + } + + private void OnItemsSourceChanged(object sender, CheckedChangedEventArgs e) + { + if (!(sender is RadioButton radioButton) || !e.Value) + return; + if (radioButton == ItemsSourceObservableCollection25) + _viewModel.ItemsSourceType = ItemsSourceType.ObservableCollection25T; + else if (radioButton == ItemsSourceObservableCollection5) + _viewModel.ItemsSourceType = ItemsSourceType.ObservableCollection5T; + else if (radioButton == ItemsSourceGroupedList) + _viewModel.ItemsSourceType = ItemsSourceType.GroupedListT; + else if (radioButton == ItemsSourceNone) + _viewModel.ItemsSourceType = ItemsSourceType.None; + } + } +} diff --git a/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewViewModel.cs b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewViewModel.cs new file mode 100644 index 000000000000..76d61f1ed6f3 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/CollectionViewViewModel.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Graphics; +using Maui.Controls.Sample.CollectionViewGalleries; + +namespace Maui.Controls.Sample +{ + public class Grouping : ObservableCollection + { + public TKey Key { get; } + + public Grouping(TKey key, IEnumerable items) : base(items) + { + Key = key; + } + } + + public enum ItemsSourceType + { + None, + ObservableCollection25T, + ObservableCollection5T, + GroupedListT, + EmptyGroupedListT, + EmptyObservableCollectionT + } + public class CollectionViewViewModel : INotifyPropertyChanged + { + private object _emptyView; + private object _header; + private object _footer; + private DataTemplate _emptyViewTemplate; + private DataTemplate _headerTemplate; + private DataTemplate _footerTemplate; + private DataTemplate _groupHeaderTemplate; + private DataTemplate _groupFooterTemplate; + private DataTemplate _itemTemplate; + private IItemsLayout _itemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical); + private ItemsSourceType _itemsSourceType = ItemsSourceType.None; + private bool _isGrouped = false; + private ObservableCollection _observableCollection25; + private ObservableCollection _observableCollection5; + private ObservableCollection _emptyObservableCollection; + private List> _groupedList; + private List> _emptyGroupedList; + public event PropertyChangedEventHandler PropertyChanged; + + public CollectionViewViewModel() + { + LoadItems(); + ItemTemplate = new DataTemplate(() => + { + var stackLayout = new StackLayout + { + Padding = new Thickness(10), + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center + }; + + var label = new Label + { + VerticalOptions = LayoutOptions.Center, + HorizontalOptions = LayoutOptions.Center + }; + label.SetBinding(Label.TextProperty, "Caption"); + stackLayout.Children.Add(label); + return stackLayout; + }); + + GroupHeaderTemplate = new DataTemplate(() => + { + var stackLayout = new StackLayout + { + BackgroundColor = Colors.LightGray + }; + var label = new Label + { + FontAttributes = FontAttributes.Bold, + FontSize = 18 + }; + label.SetBinding(Label.TextProperty, "Key"); + stackLayout.Children.Add(label); + return stackLayout; + }); + } + + public object EmptyView + { + get => _emptyView; + set { _emptyView = value; OnPropertyChanged(); } + } + + public object Header + { + get => _header; + set { _header = value; OnPropertyChanged(); } + } + + public object Footer + { + get => _footer; + set { _footer = value; OnPropertyChanged(); } + } + + public DataTemplate EmptyViewTemplate + { + get => _emptyViewTemplate; + set { _emptyViewTemplate = value; OnPropertyChanged(); } + } + + public DataTemplate HeaderTemplate + { + get => _headerTemplate; + set { _headerTemplate = value; OnPropertyChanged(); } + } + + public DataTemplate FooterTemplate + { + get => _footerTemplate; + set { _footerTemplate = value; OnPropertyChanged(); } + } + + public DataTemplate GroupHeaderTemplate + { + get => _groupHeaderTemplate; + set { _groupHeaderTemplate = value; OnPropertyChanged(); } + } + + public DataTemplate GroupFooterTemplate + { + get => _groupFooterTemplate; + set { _groupFooterTemplate = value; OnPropertyChanged(); } + } + + public DataTemplate ItemTemplate + { + get => _itemTemplate; + set { _itemTemplate = value; OnPropertyChanged(); } + } + + public IItemsLayout ItemsLayout + { + get => _itemsLayout; + set + { + if (_itemsLayout != value) + { + _itemsLayout = value; + OnPropertyChanged(); + } + } + } + + public ItemsSourceType ItemsSourceType + { + get => _itemsSourceType; + set + { + if (_itemsSourceType != value) + { + _itemsSourceType = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ItemsSource)); + } + } + } + + public bool IsGrouped + { + get => _isGrouped; + set + { + if (_isGrouped != value) + { + _isGrouped = value; + OnPropertyChanged(); + } + } + } + + public object ItemsSource + { + get + { + return ItemsSourceType switch + { + ItemsSourceType.ObservableCollection25T => _observableCollection25, + ItemsSourceType.ObservableCollection5T => _observableCollection5, + ItemsSourceType.GroupedListT => _groupedList, + ItemsSourceType.EmptyGroupedListT => _emptyGroupedList, + ItemsSourceType.EmptyObservableCollectionT => _emptyObservableCollection, + ItemsSourceType.None => null, + _ => null + }; + } + } + + + private void LoadItems() + { + _observableCollection25 = new ObservableCollection(); + _observableCollection5 = new ObservableCollection(); + AddItems(_observableCollection5, 5, "Fruits"); + AddItems(_observableCollection25, 10, "Fruits"); + AddItems(_observableCollection25, 10, "Vegetables"); + + _emptyObservableCollection = new ObservableCollection(); + + _groupedList = new List> + { + new Grouping("Fruits", new List()), + new Grouping("Vegetables", new List()) + }; + + AddItems(_groupedList[0], 4, "Fruits"); + AddItems(_groupedList[1], 4, "Vegetables"); + + _emptyGroupedList = new List>(); + } + + + private void AddItems(IList list, int count, string category) + { + string[] fruits = + { + "Apple", "Banana", "Orange", "Grapes", "Mango", + "Pineapple", "Strawberry", "Blueberry", "Peach", "Cherry", + "Watermelon", "Papaya", "Kiwi", "Pear", "Plum", + "Avocado", "Fig", "Guava", "Lychee", "Pomegranate", + "Lime", "Lemon", "Coconut", "Apricot", "Blackberry" + }; + + string[] vegetables = + { + "Carrot", "Broccoli", "Spinach", "Potato", "Tomato", + "Cucumber", "Lettuce", "Onion", "Garlic", "Pepper", + "Zucchini", "Pumpkin", "Radish", "Beetroot", "Cabbage", + "Sweet Potato", "Turnip", "Cauliflower", "Celery", "Asparagus", + "Eggplant", "Chili", "Corn", "Peas", "Mushroom" + }; + + string[] items = category == "Fruits" ? fruits : vegetables; + + for (int n = 0; n < count; n++) + { + list.Add(new CollectionViewTestItem(items[n % items.Length], n)); // Pass the index + } + } + + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + if (propertyName == nameof(IsGrouped)) + { + OnPropertyChanged(nameof(ItemsSource)); + } + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public class CustomDataTemplateSelector : DataTemplateSelector + { + public DataTemplate Template1 { get; set; } + public DataTemplate Template2 { get; set; } + + protected override DataTemplate OnSelectTemplate(object item, BindableObject container) + { + if (item is CollectionViewTestItem testItem) + { + return testItem.Index % 2 == 0 ? Template1 : Template2; + } + + return Template1; + } + } + + public class CollectionViewTestItem + { + public string Caption { get; set; } + public int Index { get; set; } + + public CollectionViewTestItem(string caption, int index) + { + Caption = caption; + Index = index; + } + } + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/HeaderFooterMainPage.xaml b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/HeaderFooterMainPage.xaml new file mode 100644 index 000000000000..c98e25007ce5 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/CollectionView/HeaderFooterMainPage.xaml @@ -0,0 +1,15 @@ + + + +