diff --git a/src/Core/src/Platform/Android/ElementExtensions.cs b/src/Core/src/Platform/Android/ElementExtensions.cs new file mode 100644 index 000000000000..6bd151b78028 --- /dev/null +++ b/src/Core/src/Platform/Android/ElementExtensions.cs @@ -0,0 +1,13 @@ +using System; +using Android.App; +using Android.Content; +using AView = Android.Views.View; + +namespace Microsoft.Maui.Platform +{ + public static partial class ElementExtensions + { + public static AView ToContainerView(this IElement view, IMauiContext context) => + new ContainerView(context) { CurrentView = view }; + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/Android/HandlerExtensions.cs b/src/Core/src/Platform/ElementExtensions.cs similarity index 53% rename from src/Core/src/Platform/Android/HandlerExtensions.cs rename to src/Core/src/Platform/ElementExtensions.cs index b1a9f10dfafe..bb140d84ac3a 100644 --- a/src/Core/src/Platform/Android/HandlerExtensions.cs +++ b/src/Core/src/Platform/ElementExtensions.cs @@ -1,46 +1,31 @@ -using System; -using Android.App; -using Android.Content; -using AView = Android.Views.View; +using System; +#if __IOS__ || MACCATALYST +using NativeView = UIKit.UIView; +using BasePlatformType = Foundation.NSObject; +using PlatformWindow = UIKit.UIWindow; +using PlatformApplication = UIKit.UIApplicationDelegate; +#elif MONOANDROID +using NativeView = Android.Views.View; +using BasePlatformType = Android.Content.Context; +using PlatformWindow = Android.App.Activity; +using PlatformApplication = Android.App.Application; +#elif WINDOWS +using NativeView = Microsoft.UI.Xaml.FrameworkElement; +using BasePlatformType = WinRT.IWinRTObject; +using PlatformWindow = Microsoft.UI.Xaml.Window; +using PlatformApplication = Microsoft.UI.Xaml.Application; +#elif NETSTANDARD || (NET6_0 && !IOS && !ANDROID) +using NativeView = System.Object; +using BasePlatformType = System.Object; +using INativeViewHandler = Microsoft.Maui.IViewHandler; +using PlatformWindow = System.Object; +using PlatformApplication = System.Object; +#endif namespace Microsoft.Maui.Platform { - public static class HandlerExtensions + public static partial class ElementExtensions { - internal static AView? GetNative(this IElement view, bool returnWrappedIfPresent) - { - if (view.Handler is INativeViewHandler nativeHandler && nativeHandler.NativeView != null) - return nativeHandler.NativeView; - - return (view.Handler?.NativeView as AView); - - } - - internal static AView ToNative(this IElement view, IMauiContext context, bool returnWrappedIfPresent) - { - var nativeView = view.ToNative(context); - - if (view.Handler is INativeViewHandler nativeHandler && nativeHandler.NativeView != null) - return nativeHandler.NativeView; - - return nativeView; - - } - - public static AView ToContainerView(this IElement view, IMauiContext context) => - new ContainerView(context) { CurrentView = view }; - - public static AView ToNative(this IElement view, IMauiContext context) - { - var handler = view.ToHandler(context); - - if (handler.NativeView is not AView result) - { - throw new InvalidOperationException($"Unable to convert {view} to {typeof(AView)}"); - } - return result; - } - public static IElementHandler ToHandler(this IElement view, IMauiContext context) { _ = view ?? throw new ArgumentNullException(nameof(view)); @@ -68,16 +53,41 @@ public static IElementHandler ToHandler(this IElement view, IMauiContext context if (handler.VirtualView != view) handler.SetVirtualView(view); - return (IElementHandler)handler; + return handler; } - public static void SetApplicationHandler(this Application nativeApplication, IApplication application, IMauiContext context) => - SetHandler(nativeApplication, application, context); + internal static NativeView? GetNative(this IElement view, bool returnWrappedIfPresent) + { + if (view.Handler is INativeViewHandler nativeHandler && nativeHandler.NativeView != null) + return nativeHandler.NativeView; + + return view.Handler?.NativeView as NativeView; + + } + + internal static NativeView ToNative(this IElement view, IMauiContext context, bool returnWrappedIfPresent) + { + var nativeView = view.ToNative(context); + + if (view.Handler is INativeViewHandler nativeHandler && nativeHandler.NativeView != null) + return nativeHandler.NativeView; + + return nativeView; - public static void SetWindowHandler(this Activity activity, IWindow window, IMauiContext context) => - SetHandler(activity, window, context); + } - static void SetHandler(this Context nativeElement, IElement element, IMauiContext context) + public static NativeView ToNative(this IElement view, IMauiContext context) + { + var handler = view.ToHandler(context); + + if (handler.NativeView is not NativeView result) + { + throw new InvalidOperationException($"Unable to convert {view} to {typeof(NativeView)}"); + } + return result; + } + + static void SetHandler(this BasePlatformType nativeElement, IElement element, IMauiContext context) { _ = nativeElement ?? throw new ArgumentNullException(nameof(nativeElement)); _ = element ?? throw new ArgumentNullException(nameof(element)); @@ -100,5 +110,11 @@ static void SetHandler(this Context nativeElement, IElement element, IMauiContex if (handler.VirtualView != element) handler.SetVirtualView(element); } + + public static void SetApplicationHandler(this PlatformApplication nativeApplication, IApplication application, IMauiContext context) => + SetHandler(nativeApplication, application, context); + + public static void SetWindowHandler(this PlatformWindow nativeWindow, IWindow window, IMauiContext context) => + SetHandler(nativeWindow, window, context); } -} \ No newline at end of file +} diff --git a/src/Core/src/Platform/ViewExtensions.cs b/src/Core/src/Platform/ViewExtensions.cs index 1a3bf4590d59..c7ad2274c078 100644 --- a/src/Core/src/Platform/ViewExtensions.cs +++ b/src/Core/src/Platform/ViewExtensions.cs @@ -1,6 +1,9 @@ using System; using System.Numerics; using Microsoft.Maui.Graphics; +#if NETSTANDARD || (NET6_0 && !IOS && !ANDROID) +using INativeViewHandler = Microsoft.Maui.IViewHandler; +#endif namespace Microsoft.Maui { @@ -13,5 +16,8 @@ public static partial class ViewExtensions internal static double ExtractAngleInRadians(this Matrix4x4 matrix) => Math.Atan2(matrix.M21, matrix.M11); internal static double ExtractAngleInDegrees(this Matrix4x4 matrix) => ExtractAngleInRadians(matrix) * 180 / Math.PI; + + public static INativeViewHandler ToHandler(this IView view, IMauiContext context) => + (INativeViewHandler)ElementExtensions.ToHandler(view, context); } } diff --git a/src/Core/src/Platform/Windows/HandlerExtensions.cs b/src/Core/src/Platform/Windows/HandlerExtensions.cs deleted file mode 100644 index 8c7d562ab90e..000000000000 --- a/src/Core/src/Platform/Windows/HandlerExtensions.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using Microsoft.UI.Xaml; - -namespace Microsoft.Maui.Platform -{ - public static class HandlerExtensions - { - internal static FrameworkElement? GetNative(this IElement view, bool returnWrappedIfPresent) - { - if (view.Handler is INativeViewHandler nativeHandler && nativeHandler.NativeView != null) - return nativeHandler.NativeView; - - return (view.Handler?.NativeView as FrameworkElement); - - } - - internal static FrameworkElement ToNative(this IElement view, IMauiContext context, bool returnWrappedIfPresent) - { - var nativeView = view.ToNative(context); - - if (view.Handler is INativeViewHandler nativeHandler && nativeHandler.NativeView != null) - return nativeHandler.NativeView; - - return nativeView; - - } - - public static FrameworkElement ToNative(this IElement view, IMauiContext context) - { - _ = view ?? throw new ArgumentNullException(nameof(view)); - _ = context ?? throw new ArgumentNullException(nameof(context)); - - //This is how MVU works. It collapses views down - if (view is IReplaceableView ir) - view = ir.ReplacedView; - - var handler = view.Handler; - - if (handler == null) - handler = context.Handlers.GetHandler(view.GetType()); - - if (handler == null) - throw new Exception($"Handler not found for view {view}."); - - handler.SetMauiContext(context); - - view.Handler = handler; - - if (handler.VirtualView != view) - handler.SetVirtualView(view); - - if (handler is INativeViewHandler nvh && nvh.NativeView is FrameworkElement) - return nvh.NativeView; - - if (handler.NativeView is FrameworkElement result) - return result; - - throw new InvalidOperationException($"Unable to convert {view} to {typeof(FrameworkElement)}"); - } - - public static void SetApplicationHandler(this UI.Xaml.Application nativeApplication, IApplication application, IMauiContext context) => - SetHandler(nativeApplication, application, context); - - public static void SetWindowHandler(this UI.Xaml.Window nativeWindow, IWindow window, IMauiContext context) => - SetHandler(nativeWindow, window, context); - - static void SetHandler(this WinRT.IWinRTObject nativeElement, IElement element, IMauiContext context) - { - _ = nativeElement ?? throw new ArgumentNullException(nameof(nativeElement)); - _ = element ?? throw new ArgumentNullException(nameof(element)); - _ = context ?? throw new ArgumentNullException(nameof(context)); - - var handler = element.Handler; - if (handler == null) - handler = context.Handlers.GetHandler(element.GetType()); - - if (handler == null) - throw new Exception($"Handler not found for window {element}."); - - handler.SetMauiContext(context); - - element.Handler = handler; - - if (handler.VirtualView != element) - handler.SetVirtualView(element); - } - } -} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/ElementExtensions.cs b/src/Core/src/Platform/iOS/ElementExtensions.cs new file mode 100644 index 000000000000..aa66185c39c8 --- /dev/null +++ b/src/Core/src/Platform/iOS/ElementExtensions.cs @@ -0,0 +1,27 @@ +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Microsoft.Maui.Platform +{ + public static partial class ElementExtensions + { + const string UIApplicationSceneManifestKey = "UIApplicationSceneManifest"; + + public static UIViewController ToUIViewController(this IElement view, IMauiContext context) + { + var nativeView = view.ToNative(context); + if (view?.Handler is INativeViewHandler nvh && nvh.ViewController != null) + return nvh.ViewController; + + return new ContainerViewController { CurrentView = view, Context = context }; + } + + // If < iOS 13 or the Info.plist does not have a scene manifest entry we need to assume no multi window, and no UISceneDelegate. + // We cannot check for iPads/Mac because even on the iPhone it uses the scene delegate if one is specified in the manifest. + public static bool HasSceneManifest(this UIApplicationDelegate nativeApplication) => + UIDevice.CurrentDevice.CheckSystemVersion(13, 0) && + NSBundle.MainBundle.InfoDictionary.ContainsKey(new NSString(UIApplicationSceneManifestKey)); + } +} diff --git a/src/Core/src/Platform/iOS/HandlerExtensions.cs b/src/Core/src/Platform/iOS/HandlerExtensions.cs deleted file mode 100644 index d7ca227a8348..000000000000 --- a/src/Core/src/Platform/iOS/HandlerExtensions.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using Foundation; -using ObjCRuntime; -using UIKit; - -namespace Microsoft.Maui.Platform -{ - public static class HandlerExtensions - { - const string UIApplicationSceneManifestKey = "UIApplicationSceneManifest"; - - internal static UIView? GetNative(this IElement view, bool returnWrappedIfPresent) - { - if (view.Handler is INativeViewHandler nativeHandler && nativeHandler.NativeView != null) - return nativeHandler.NativeView; - - return (view.Handler?.NativeView as UIView); - - } - - internal static UIView ToNative(this IElement view, IMauiContext context, bool returnWrappedIfPresent) - { - var nativeView = view.ToNative(context); - - if (view.Handler is INativeViewHandler nativeHandler && nativeHandler.NativeView != null) - return nativeHandler.NativeView; - - return nativeView; - - } - - public static UIViewController ToUIViewController(this IElement view, IMauiContext context) - { - var nativeView = view.ToNative(context); - if (view?.Handler is INativeViewHandler nvh && nvh.ViewController != null) - return nvh.ViewController; - - return new ContainerViewController { CurrentView = view, Context = context }; - } - - public static UIView ToNative(this IElement view, IMauiContext context) - { - var handler = view.ToHandler(context); - - if (handler.NativeView is not UIView result) - { - throw new InvalidOperationException($"Unable to convert {view} to {typeof(UIView)}"); - } - - return result; - } - - public static INativeViewHandler ToHandler(this IElement view, IMauiContext context) - { - _ = view ?? throw new ArgumentNullException(nameof(view)); - _ = context ?? throw new ArgumentNullException(nameof(context)); - - //This is how MVU works. It collapses views down - if (view is IReplaceableView ir) - view = ir.ReplacedView; - - var handler = view.Handler; - if (handler == null) - handler = context.Handlers.GetHandler(view.GetType()); - - if (handler == null) - throw new Exception($"Handler not found for view {view}."); - - handler.SetMauiContext(context); - - view.Handler = handler; - - if (handler.VirtualView != view) - handler.SetVirtualView(view); - - return (INativeViewHandler)handler; - } - - // If < iOS 13 or the Info.plist does not have a scene manifest entry we need to assume no multi window, and no UISceneDelegate. - // We cannot check for iPads/Mac because even on the iPhone it uses the scene delegate if one is specified in the manifest. - public static bool HasSceneManifest(this UIApplicationDelegate nativeApplication) => - UIDevice.CurrentDevice.CheckSystemVersion(13, 0) && - NSBundle.MainBundle.InfoDictionary.ContainsKey(new NSString(UIApplicationSceneManifestKey)); - - public static void SetApplicationHandler(this UIApplicationDelegate nativeApplication, IApplication application, IMauiContext context) => - SetHandler(nativeApplication, application, context); - - public static void SetWindowHandler(this UIWindow nativeWindow, IWindow window, IMauiContext context) => - SetHandler(nativeWindow, window, context); - - static void SetHandler(this NSObject nativeElement, IElement element, IMauiContext mauiContext) - { - _ = nativeElement ?? throw new ArgumentNullException(nameof(nativeElement)); - _ = element ?? throw new ArgumentNullException(nameof(element)); - _ = mauiContext ?? throw new ArgumentNullException(nameof(mauiContext)); - - var handler = element.Handler; - if (handler == null) - handler = mauiContext.Handlers.GetHandler(element.GetType()); - - if (handler == null) - throw new Exception($"Handler not found for window {element}."); - - handler.SetMauiContext(mauiContext); - - element.Handler = handler; - - if (handler.VirtualView != element) - handler.SetVirtualView(element); - } - } -} diff --git a/src/Core/tests/DeviceTests/Handlers/Element/ElementTests.cs b/src/Core/tests/DeviceTests/Handlers/Element/ElementTests.cs new file mode 100644 index 000000000000..54da7d2497e4 --- /dev/null +++ b/src/Core/tests/DeviceTests/Handlers/Element/ElementTests.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Maui.DeviceTests.Stubs; +using Xunit; + +namespace Microsoft.Maui.DeviceTests +{ + public partial class ElementTests : HandlerTestBase + { + [Fact] + public void ElementToHandlerReturnsIElementHandler() + { + var handler = new ElementStub().ToHandler(MauiContext); + Assert.NotNull(handler); + } + } +} diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.cs index 3d7058e1c500..b6385692204b 100644 --- a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.cs +++ b/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.cs @@ -8,15 +8,16 @@ namespace Microsoft.Maui.DeviceTests { - public partial class HandlerTestBase : TestBase, IDisposable - where THandler : IViewHandler, new() - where TStub : StubBase, IView, new() + public class HandlerTestBase : TestBase, IDisposable { IApplication _app; MauiApp _mauiApp; IServiceProvider _servicesProvider; IMauiContext _context; - static readonly Random rnd = new Random(); + + public IApplication App => _app; + + public IMauiContext MauiContext => _context; public HandlerTestBase() { @@ -27,6 +28,7 @@ public HandlerTestBase() handlers.AddHandler(typeof(ButtonWithContainerStub), typeof(ButtonWithContainerStubHandler)); handlers.AddHandler(typeof(SliderStub), typeof(SliderHandler)); handlers.AddHandler(typeof(ButtonStub), typeof(ButtonHandler)); + handlers.AddHandler(typeof(ElementStub), typeof(ElementHandlerStub)); }) .ConfigureImageSources(services => { @@ -49,19 +51,6 @@ public HandlerTestBase() _context = new ContextStub(_servicesProvider); } - public static async Task Wait(Func exitCondition, int timeout = 1000) - { - while ((timeout -= 100) > 0) - { - if (!exitCondition.Invoke()) - await Task.Delay(rnd.Next(100, 200)); - else - break; - } - - return exitCondition.Invoke(); - } - public void Dispose() { ((IDisposable)_mauiApp).Dispose(); @@ -70,154 +59,5 @@ public void Dispose() _app = null; _context = null; } - - public IApplication App => _app; - - public IMauiContext MauiContext => _context; - - protected THandler CreateHandler(IView view, IMauiContext mauiContext = null) => - CreateHandler(view, mauiContext); - - protected TCustomHandler CreateHandler(IView view, IMauiContext mauiContext = null) - where TCustomHandler : THandler, new() - { - var handler = new TCustomHandler(); - InitializeViewHandler(view, handler, mauiContext); - return handler; - } - - protected void InitializeViewHandler(IView view, IViewHandler handler, IMauiContext mauiContext = null) - { - handler.SetMauiContext(mauiContext ?? MauiContext); - - handler.SetVirtualView(view); - view.Handler = handler; - - view.Arrange(new Rectangle(0, 0, view.Width, view.Height)); - handler.NativeArrange(view.Frame); - } - - protected async Task CreateHandlerAsync(IView view) - { - return await InvokeOnMainThreadAsync(() => - { - return CreateHandler(view); - }); - } - - protected Task GetValueAsync(IView view, Func func) - { - return InvokeOnMainThreadAsync(() => - { - var handler = CreateHandler(view); - return func(handler); - }); - } - - protected Task SetValueAsync(IView view, TValue value, Action func) - { - return InvokeOnMainThreadAsync(() => - { - var handler = CreateHandler(view); - func(handler, value); - }); - } - - async protected Task ValidatePropertyInitValue( - IView view, - Func GetValue, - Func GetNativeValue, - TValue expectedValue) - { - var values = await GetValueAsync(view, (handler) => - { - return new - { - ViewValue = GetValue(), - NativeViewValue = GetNativeValue(handler) - }; - }); - - Assert.Equal(expectedValue, values.ViewValue); - Assert.Equal(expectedValue, values.NativeViewValue); - } - - async protected Task ValidatePropertyUpdatesValue( - IView view, - string property, - Func GetNativeValue, - TValue expectedSetValue, - TValue expectedUnsetValue) - { - var propInfo = view.GetType().GetProperty(property); - - // set initial values - - propInfo.SetValue(view, expectedSetValue); - - var (handler, viewVal, nativeVal) = await InvokeOnMainThreadAsync(() => - { - var handler = CreateHandler(view); - return (handler, (TValue)propInfo.GetValue(view), GetNativeValue(handler)); - }); - - Assert.Equal(expectedSetValue, viewVal); - Assert.Equal(expectedSetValue, nativeVal); - - // confirm can update - - (viewVal, nativeVal) = await InvokeOnMainThreadAsync(() => - { - propInfo.SetValue(view, expectedUnsetValue); - handler.UpdateValue(property); - - return ((TValue)propInfo.GetValue(view), GetNativeValue(handler)); - }); - - Assert.Equal(expectedUnsetValue, viewVal); - Assert.Equal(expectedUnsetValue, nativeVal); - - // confirm can revert - - (viewVal, nativeVal) = await InvokeOnMainThreadAsync(() => - { - propInfo.SetValue(view, expectedSetValue); - handler.UpdateValue(property); - - return ((TValue)propInfo.GetValue(view), GetNativeValue(handler)); - }); - - Assert.Equal(expectedSetValue, viewVal); - Assert.Equal(expectedSetValue, nativeVal); - } - - async protected Task ValidateUnrelatedPropertyUnaffected( - IView view, - Func GetNativeValue, - string property, - Action SetUnrelatedProperty) - { - // get initial values - - var (handler, initialNativeVal) = await InvokeOnMainThreadAsync(() => - { - var handler = CreateHandler(view); - return (handler, GetNativeValue(handler)); - }); - - // run update - - var newNativeVal = await InvokeOnMainThreadAsync(() => - { - SetUnrelatedProperty(); - handler.UpdateValue(property); - - return GetNativeValue(handler); - }); - - // ensure unchanged - - Assert.Equal(initialNativeVal, newNativeVal); - } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Android.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.Android.cs similarity index 100% rename from src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Android.cs rename to src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.Android.cs diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Tests.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.Tests.cs similarity index 100% rename from src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Tests.cs rename to src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.Tests.cs diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Windows.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.Windows.cs similarity index 100% rename from src/Core/tests/DeviceTests/Handlers/HandlerTestBase.Windows.cs rename to src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.Windows.cs diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.cs new file mode 100644 index 000000000000..2c23303469cc --- /dev/null +++ b/src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.cs @@ -0,0 +1,175 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Maui.DeviceTests.Stubs; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Handlers; +using Microsoft.Maui.Hosting; +using Xunit; + +namespace Microsoft.Maui.DeviceTests +{ + public partial class HandlerTestBase : HandlerTestBase + where THandler : IViewHandler, new() + where TStub : StubBase, IView, new() + { + static readonly Random rnd = new Random(); + + + public static async Task Wait(Func exitCondition, int timeout = 1000) + { + while ((timeout -= 100) > 0) + { + if (!exitCondition.Invoke()) + await Task.Delay(rnd.Next(100, 200)); + else + break; + } + + return exitCondition.Invoke(); + } + protected THandler CreateHandler(IView view, IMauiContext mauiContext = null) => + CreateHandler(view, mauiContext); + + protected TCustomHandler CreateHandler(IView view, IMauiContext mauiContext = null) + where TCustomHandler : THandler, new() + { + var handler = new TCustomHandler(); + InitializeViewHandler(view, handler, mauiContext); + return handler; + } + + protected void InitializeViewHandler(IView view, IViewHandler handler, IMauiContext mauiContext = null) + { + handler.SetMauiContext(mauiContext ?? MauiContext); + + handler.SetVirtualView(view); + view.Handler = handler; + + view.Arrange(new Rectangle(0, 0, view.Width, view.Height)); + handler.NativeArrange(view.Frame); + } + + protected async Task CreateHandlerAsync(IView view) + { + return await InvokeOnMainThreadAsync(() => + { + return CreateHandler(view); + }); + } + + protected Task GetValueAsync(IView view, Func func) + { + return InvokeOnMainThreadAsync(() => + { + var handler = CreateHandler(view); + return func(handler); + }); + } + + protected Task SetValueAsync(IView view, TValue value, Action func) + { + return InvokeOnMainThreadAsync(() => + { + var handler = CreateHandler(view); + func(handler, value); + }); + } + + async protected Task ValidatePropertyInitValue( + IView view, + Func GetValue, + Func GetNativeValue, + TValue expectedValue) + { + var values = await GetValueAsync(view, (handler) => + { + return new + { + ViewValue = GetValue(), + NativeViewValue = GetNativeValue(handler) + }; + }); + + Assert.Equal(expectedValue, values.ViewValue); + Assert.Equal(expectedValue, values.NativeViewValue); + } + + async protected Task ValidatePropertyUpdatesValue( + IView view, + string property, + Func GetNativeValue, + TValue expectedSetValue, + TValue expectedUnsetValue) + { + var propInfo = view.GetType().GetProperty(property); + + // set initial values + + propInfo.SetValue(view, expectedSetValue); + + var (handler, viewVal, nativeVal) = await InvokeOnMainThreadAsync(() => + { + var handler = CreateHandler(view); + return (handler, (TValue)propInfo.GetValue(view), GetNativeValue(handler)); + }); + + Assert.Equal(expectedSetValue, viewVal); + Assert.Equal(expectedSetValue, nativeVal); + + // confirm can update + + (viewVal, nativeVal) = await InvokeOnMainThreadAsync(() => + { + propInfo.SetValue(view, expectedUnsetValue); + handler.UpdateValue(property); + + return ((TValue)propInfo.GetValue(view), GetNativeValue(handler)); + }); + + Assert.Equal(expectedUnsetValue, viewVal); + Assert.Equal(expectedUnsetValue, nativeVal); + + // confirm can revert + + (viewVal, nativeVal) = await InvokeOnMainThreadAsync(() => + { + propInfo.SetValue(view, expectedSetValue); + handler.UpdateValue(property); + + return ((TValue)propInfo.GetValue(view), GetNativeValue(handler)); + }); + + Assert.Equal(expectedSetValue, viewVal); + Assert.Equal(expectedSetValue, nativeVal); + } + + async protected Task ValidateUnrelatedPropertyUnaffected( + IView view, + Func GetNativeValue, + string property, + Action SetUnrelatedProperty) + { + // get initial values + + var (handler, initialNativeVal) = await InvokeOnMainThreadAsync(() => + { + var handler = CreateHandler(view); + return (handler, GetNativeValue(handler)); + }); + + // run update + + var newNativeVal = await InvokeOnMainThreadAsync(() => + { + SetUnrelatedProperty(); + handler.UpdateValue(property); + + return GetNativeValue(handler); + }); + + // ensure unchanged + + Assert.Equal(initialNativeVal, newNativeVal); + } + } +} \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/HandlerTestBase.iOS.cs b/src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.iOS.cs similarity index 100% rename from src/Core/tests/DeviceTests/Handlers/HandlerTestBase.iOS.cs rename to src/Core/tests/DeviceTests/Handlers/HandlerTestBaseOfT.iOS.cs diff --git a/src/Core/tests/DeviceTests/Stubs/ElementHandlerStub.cs b/src/Core/tests/DeviceTests/Stubs/ElementHandlerStub.cs new file mode 100644 index 000000000000..b1aa77f8ac2d --- /dev/null +++ b/src/Core/tests/DeviceTests/Stubs/ElementHandlerStub.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Maui.DeviceTests.Stubs; + +namespace Microsoft.Maui.DeviceTests +{ + public class ElementHandlerStub : ElementHandler + { + public ElementHandlerStub() : base(ElementHandler.ElementMapper) + { + + } + + protected override object CreateNativeElement() => new Object(); + } +} diff --git a/src/Core/tests/DeviceTests/Stubs/ElementStub.cs b/src/Core/tests/DeviceTests/Stubs/ElementStub.cs new file mode 100644 index 000000000000..e98c6ea4169e --- /dev/null +++ b/src/Core/tests/DeviceTests/Stubs/ElementStub.cs @@ -0,0 +1,9 @@ +namespace Microsoft.Maui.DeviceTests.Stubs +{ + public class ElementStub : IElement + { + public IElement Parent { get; set; } + + public IElementHandler Handler { get; set; } + } +} diff --git a/src/Core/tests/DeviceTests/Stubs/StubBase.cs b/src/Core/tests/DeviceTests/Stubs/StubBase.cs index 113936ae395a..9d4a85dce5ce 100644 --- a/src/Core/tests/DeviceTests/Stubs/StubBase.cs +++ b/src/Core/tests/DeviceTests/Stubs/StubBase.cs @@ -7,7 +7,7 @@ namespace Microsoft.Maui.DeviceTests.Stubs { - public class StubBase : IView, IVisualTreeElement + public class StubBase : ElementStub, IView, IVisualTreeElement { IElementHandler IElement.Handler { @@ -27,14 +27,16 @@ IElementHandler IElement.Handler public Rectangle Frame { get; set; } - public IViewHandler Handler { get; set; } + public new IViewHandler Handler + { + get => (IViewHandler)base.Handler; + set => base.Handler = value; + } public IShape Clip { get; set; } public IShadow Shadow { get; set; } - public IElement Parent { get; set; } - public Size DesiredSize { get; set; } = new Size(50, 50); public double Width { get; set; } = 50;