diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 95cf5eb6b60..b95a16f1052 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -23,11 +23,13 @@ Follow the style used by the [.NET Foundation](https://github.com/dotnet/corefx/ - We do not use the `private` keyword, as it is the default accessibility level in C#. - We use hard tabs over spaces. -Read and follow our [Pull Request template](https://github.com/xamarin/Xamarin.Forms/blob/master/PULL_REQUEST_TEMPLATE.md). +Read and follow our [Pull Request template](PULL_REQUEST_TEMPLATE.md). ### Pull Request Requirements - We use red-green-refactor test driven development. If you're planning to work on a bug fix, please be sure to create a test case in the UI tests suite (or unit tests, if you're working on Core/XAML code) that proves that the behavior is broken and then proves that the behavior was resolved after your changes. If at all possible, the test should be automated. If the test cannot be automated, then it should include manual testing instructions on screen. +We use red-green-refactor test driven development. If you're planning to work on a bug fix, please be sure to create a test case in the UI tests suite (or unit tests, if you're working on Core/XAML code) that proves that the behavior is broken and then proves that the behavior was resolved after your changes. If at all possible, the test should be automated. If the test cannot be automated, then it should include manual testing instructions on screen. + +Please check the "Allow edits from maintainers" checkbox on your pull request. This allows us to quickly make minor fixes and resolve conflicts for you. ## Proposals/Enhancements/Suggestions diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 459283efe54..5d9648e7786 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -41,3 +41,7 @@ assignees: '' ### Reproduction Link + +### Workaround + + \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e86f3ba4311..9e5e02a2d96 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ - If this is an enhancement or contains API changes or breaking changes, target master. - If the issue you're working on has a milestone, target the corresponding branch. - If this is a bug fix, target the branch of the latest stable version (unless the bug is only in a prerelease or master, of course!). - See [Contributing](https://github.com/xamarin/Xamarin.Forms/blob/master/CONTRIBUTING.md) for more tips! + See [Contributing](https://github.com/xamarin/Xamarin.Forms/blob/master/.github/CONTRIBUTING.md) for more tips! PLEASE DELETE THE ALL THESE COMMENTS BEFORE SUBMITTING! THANKS!!! --> diff --git a/.nuspec/Xamarin.Forms.nuspec b/.nuspec/Xamarin.Forms.nuspec index a8797123569..ccb1023b87e 100644 --- a/.nuspec/Xamarin.Forms.nuspec +++ b/.nuspec/Xamarin.Forms.nuspec @@ -272,6 +272,7 @@ + @@ -292,6 +293,7 @@ + diff --git a/.nuspec/Xamarin.Forms.props b/.nuspec/Xamarin.Forms.props index 54c037276a4..1b0a2285d13 100644 --- a/.nuspec/Xamarin.Forms.props +++ b/.nuspec/Xamarin.Forms.props @@ -12,4 +12,8 @@ --> + + <_IsHotRestartDefined>$([System.Text.RegularExpressions.Regex]::IsMatch('$(DefineConstants.Trim())', '(^|;)HOTRESTART($|;)')) + HOTRESTART;$(DefineConstants) + diff --git a/GitInfo.txt b/GitInfo.txt index a84947d6ffe..6016e8addc4 100644 --- a/GitInfo.txt +++ b/GitInfo.txt @@ -1 +1 @@ -4.5.0 +4.6.0 diff --git a/README.md b/README.md index 782e51d523a..fdb3954bea4 100644 --- a/README.md +++ b/README.md @@ -48,23 +48,23 @@ If you want to use the latest dev build then you should read [this blog post](ht ## Getting Started ## ### Windows ### -##### Install Visual Studio 2017+ ##### +##### Install Visual Studio 2019+ ##### -VS 2017+ is required for developing Xamarin.Forms. If you do not already have it installed, you can download it [here](https://www.visualstudio.com/downloads/download-visual-studio-vs). VS 2017+ Community is completely free. If you are installing VS 2017+ for the first time, select the "Custom" installation type and select the following from the features list to install: +VS 2019+ is required for developing Xamarin.Forms. If you do not already have it installed, you can download it [here](https://www.visualstudio.com/downloads/download-visual-studio-vs). VS 2019+ Community is completely free. If you are installing VS 2019+ for the first time, select the "Custom" installation type and select the following from the features list to install: - .NET desktop development - In the `Summary > Optional select .NET Framework 4.7 SDK, .NET Framework 4.7 targeting pack`. - Universal Windows Platform Development - In the `Summary > Optional select the Windows 10 Mobile Emulator`. - Mobile Development with .NET - In the `Summary > Optional select Xamarin Remoted Simulator, Xamarin SDK Manager, Intel Hardware Accelerated Execution Manager (HAXM)` -The Android 7.0 Nougat API 24 SDK is required for developing Xamarin.Forms. It can be installed by using the [Xamarin Android SDK Manager](https://docs.microsoft.com/xamarin/android/get-started/installation/android-sdk). +The Android 10.0 API 29 SDK is required for developing Xamarin.Forms. It can be installed by using the [Xamarin Android SDK Manager](https://docs.microsoft.com/xamarin/android/get-started/installation/android-sdk). We also recommend installing [Xamarin Android Device Manager](https://developer.xamarin.com/guides/android/getting_started/installation/android-emulator/xamarin-device-manager/) This will use the HAXM tools installed above and allow you to configure Android Virtual Devices (AVDs) that emulate Android devices. -If you already have VS 2017+ installed, you can verify that these features are installed by modifying the VS 2017+ installation via the Visual Studio Installer. +If you already have VS 2019+ installed, you can verify that these features are installed by modifying the VS 2019+ installation via the Visual Studio Installer. ### Mac ### #### Install Visual Studio for Mac 2019 #### -If you do not already have it installed, instructions to download and setup can be found [here](https://docs.microsoft.com/en-us/visualstudio/mac/installation?view=vsmac-2017). +If you do not already have it installed, instructions to download and setup can be found [here](https://docs.microsoft.com/en-us/visualstudio/mac/installation?view=vsmac-2019). Because of current Multi-Targeting limitations with Visual Studio for Mac you will need to manually build/restore some projects before you are able to work on the Xamarin Forms solution. @@ -92,7 +92,7 @@ By default, the `Xamarin.Forms.Controls` project does not have a configuration f UWPMapsAuthKey: -You will have to obtain your own API keys for each of these services, inserted directly after the identifier (e.g. `UWPMapsAuthKey:abcdefghijklmnopqrstuvwxyz`). You can find out how to obtain each of these as follows: +If you aren't working with maps, you can ignore this. If you want to work with maps, you will have to obtain your own API keys for each of these services, inserted directly after the identifier (e.g. `UWPMapsAuthKey:abcdefghijklmnopqrstuvwxyz`). You can find out how to obtain each of these as follows: - `UWPMapsAuthKey` at https://microsoft.com/maps/create-a-bing-maps-key.aspx @@ -134,7 +134,7 @@ You should now be able to run any of the UWP UI Tests. We follow the style used by the [.NET Foundation](https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md), with a few exceptions: - We do not use the `private` keyword as it is the default accessibility level in C#. -- We use hard tabs over spaces. You can change this setting in VS 2015 via `Tools > Options` and navigating to `Text Editor > C#` and selecting the "Keep tabs" radio option. In Visual Studio for Mac it's set via preferences in `Source Code > Code Formatting > C# source code` and disabling the checkbox for `Convert tabs to spaces`. +- We use hard tabs over spaces. You can change this setting in Visual Studio for Windows via `Tools > Options` and navigating to `Text Editor > C#` and selecting the "Keep tabs" radio option. In Visual Studio for Mac it's set via preferences in `Source Code > Code Formatting > C# source code` and disabling the checkbox for `Convert tabs to spaces`. - Lines should be limited to a max of 120 characters (or as close as possible within reason). This may be set in Visual Studio for Mac via preferences in `Source Code > Code Formatting > C# source code` and changing the `Desired file width` to `120`. ## Contributing ## diff --git a/Stubs/Xamarin.Forms.Platform.cs b/Stubs/Xamarin.Forms.Platform.cs index e41c2937cf6..ef9b75db969 100644 --- a/Stubs/Xamarin.Forms.Platform.cs +++ b/Stubs/Xamarin.Forms.Platform.cs @@ -54,6 +54,13 @@ internal class _ButtonRenderer { } [RenderWith(typeof(ImageButtonRenderer))] internal class _ImageButtonRenderer { } +#if __ANDROID__ + [RenderWith(typeof(RadioButtonRenderer))] +#elif !TIZEN4_0 + [RenderWith(typeof(RadioButtonRenderer))] +#endif + internal class _RadioButtonRenderer { } + [RenderWith (typeof (TableViewRenderer))] internal class _TableViewRenderer { } diff --git a/Xamarin.Forms.ControlGallery.Android/Activity1.cs b/Xamarin.Forms.ControlGallery.Android/Activity1.cs index 24c3744a6b3..11b923db5a8 100644 --- a/Xamarin.Forms.ControlGallery.Android/Activity1.cs +++ b/Xamarin.Forms.ControlGallery.Android/Activity1.cs @@ -21,7 +21,6 @@ [assembly: Dependency (typeof (TestCloudService))] [assembly: ExportRenderer (typeof (DisposePage), typeof (DisposePageRenderer))] [assembly: ExportRenderer (typeof (DisposeLabel), typeof (DisposeLabelRenderer))] -[assembly: ExportRenderer (typeof (CustomButton), typeof (CustomButtonRenderer))] [assembly: ExportEffect (typeof (BorderEffect), "BorderEffect")] namespace Xamarin.Forms.ControlGallery.Android diff --git a/Xamarin.Forms.ControlGallery.Android/Assets/googlemap.html b/Xamarin.Forms.ControlGallery.Android/Assets/googlemap.html new file mode 100644 index 00000000000..165e4e90c65 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Assets/googlemap.html @@ -0,0 +1,49 @@ + + + + + + Google Maps + + + + +
map
+ + diff --git a/Xamarin.Forms.ControlGallery.Android/Assets/googlemapsearch.html b/Xamarin.Forms.ControlGallery.Android/Assets/googlemapsearch.html new file mode 100644 index 00000000000..a31af760957 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Assets/googlemapsearch.html @@ -0,0 +1,84 @@ + + + + Place Searches + + + + + + +
+ + + \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/ContentDescriptionEffectRenderer.cs b/Xamarin.Forms.ControlGallery.Android/ContentDescriptionEffectRenderer.cs new file mode 100644 index 00000000000..18c1fe15592 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/ContentDescriptionEffectRenderer.cs @@ -0,0 +1,58 @@ +using System.ComponentModel; +using AViews = Android.Views; +using AWidget = Android.Widget; +using Xamarin.Forms; +using Xamarin.Forms.ControlGallery.Android; +using Xamarin.Forms.Controls.Issues; +using Xamarin.Forms.Platform.Android; +using Android.Support.V4.View; +using Android.Support.V4.View.Accessibility; +using Android.AccessibilityServices; + +[assembly: ExportEffect(typeof(ContentDescriptionEffectRenderer), ContentDescriptionEffect.EffectName)] +namespace Xamarin.Forms.ControlGallery.Android +{ + public class ContentDescriptionEffectRenderer : PlatformEffect + { + protected override void OnAttached() + { + } + + protected override void OnDetached() + { + } + + protected override void OnElementPropertyChanged(PropertyChangedEventArgs args) + { + System.Diagnostics.Debug.WriteLine("OnElementPropertyChanged" + args.PropertyName); + + var viewGroup = Control as AViews.ViewGroup; + var nativeView = Control as AViews.View; + + if (nativeView != null && viewGroup != null && viewGroup.ChildCount > 0) + { + nativeView = viewGroup.GetChildAt(0); + } + + if (nativeView == null) + { + return; + } + + var info = AccessibilityNodeInfoCompat.Obtain(nativeView); + ViewCompat.OnInitializeAccessibilityNodeInfo(nativeView, info); + + System.Diagnostics.Debug.WriteLine(info.ContentDescription); + System.Diagnostics.Debug.WriteLine(nativeView.ContentDescription); + + Element.SetValue( + ContentDescriptionEffectProperties.NameAndHelpTextProperty, + info.ContentDescription); + + Element.SetValue( + ContentDescriptionEffectProperties.ContentDescriptionProperty, + nativeView.ContentDescription); + } + + } +} diff --git a/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs b/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs index e4679c8d6c2..a3192b36c11 100644 --- a/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs +++ b/Xamarin.Forms.ControlGallery.Android/CustomRenderers.cs @@ -57,6 +57,7 @@ [assembly: ExportRenderer(typeof(Issue7249Switch), typeof(Issue7249SwitchRenderer))] [assembly: ExportRenderer(typeof(Issue9360.Issue9360NavigationPage), typeof(Issue9360NavigationPageRenderer))] [assembly: ExportRenderer(typeof(Xamarin.Forms.Controls.GalleryPages.TwoPaneViewGalleries.HingeAngleLabel), typeof(HingeAngleLabelRenderer))] +[assembly: ExportRenderer(typeof(Xamarin.Forms.Controls.Tests.TestClasses.CustomButton), typeof(CustomButtonRenderer))] #if PRE_APPLICATION_CLASS #elif FORMS_APPLICATION_ACTIVITY diff --git a/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_checked.png b/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_checked.png new file mode 100644 index 00000000000..b3438bae2fc Binary files /dev/null and b/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_checked.png differ diff --git a/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_unchecked.png b/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_unchecked.png new file mode 100644 index 00000000000..bc3abd78be7 Binary files /dev/null and b/Xamarin.Forms.ControlGallery.Android/Resources/drawable/rb_unchecked.png differ diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/AssertionExtensions.cs b/Xamarin.Forms.ControlGallery.Android/Tests/AssertionExtensions.cs new file mode 100644 index 00000000000..31ec596eeca --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Tests/AssertionExtensions.cs @@ -0,0 +1,122 @@ +using AView = Android.Views.View; +using AColor = Android.Graphics.Color; +using Android.Graphics; +using System; +using NUnit.Framework; +using System.IO; + +#if __ANDROID_29__ +#else +using Android.Support.V7.Widget; +#endif + +namespace Xamarin.Forms.ControlGallery.Android.Tests +{ + internal static class AssertionExtensions + { + public static string CreateColorAtPointError(this Bitmap bitmap, AColor expectedColor, int x, int y) + { + using (var ms = new MemoryStream()) + { + bitmap.Compress(Bitmap.CompressFormat.Png, 0, ms); + var imageAsString = Convert.ToBase64String(ms.ToArray()); + return $"Expected {expectedColor} at point {x},{y} in renderered view. This is what it looked like:{imageAsString}"; + } + } + + public static AColor ColorAtPoint(this Bitmap bitmap, int x, int y) + { + int pixel = bitmap.GetPixel(x, y); + + int red = AColor.GetRedComponent(pixel); + int blue = AColor.GetBlueComponent(pixel); + int green = AColor.GetGreenComponent(pixel); + + return AColor.Rgb(red, green, blue); + } + + public static Bitmap ToBitmap(this AView view) + { + var bitmap = Bitmap.CreateBitmap(view.Width, view.Height, Bitmap.Config.Argb8888); + var canvas = new Canvas(bitmap); + canvas.Save(); + canvas.Translate(0, 0); + view.Draw(canvas); + canvas.Restore(); + + return bitmap; + } + + public static Bitmap AssertColorAtPoint(this Bitmap bitmap, AColor expectedColor, int x, int y) + { + Assert.That(bitmap.ColorAtPoint(x, y), Is.EqualTo(expectedColor), + () => bitmap.CreateColorAtPointError(expectedColor, x, y)); + + return bitmap; + } + + public static Bitmap AssertColorAtCenter(this Bitmap bitmap, AColor expectedColor) + { + return bitmap.AssertColorAtPoint(expectedColor, bitmap.Width / 2, bitmap.Height / 2); + } + + public static Bitmap AssertColorAtBottomLeft(this Bitmap bitmap, AColor expectedColor) + { + return bitmap.AssertColorAtPoint(expectedColor, 0, 0); + } + + public static Bitmap AssertColorAtBottomRight(this Bitmap bitmap, AColor expectedColor) + { + return bitmap.AssertColorAtPoint(expectedColor, bitmap.Width - 1, 0); + } + + public static Bitmap AssertColorAtTopLeft(this Bitmap bitmap, AColor expectedColor) + { + return bitmap.AssertColorAtPoint(expectedColor, 0, bitmap.Height - 1); + } + + public static Bitmap AssertColorAtTopRight(this Bitmap bitmap, AColor expectedColor) + { + return bitmap.AssertColorAtPoint(expectedColor, bitmap.Width - 1, bitmap.Height - 1); + } + + public static Bitmap AssertColorAtPoint(this AView view, AColor expectedColor, int x, int y) + { + var bitmap = view.ToBitmap(); + Assert.That(bitmap.ColorAtPoint(x, y), Is.EqualTo(expectedColor), + () => bitmap.CreateColorAtPointError(expectedColor, x, y)); + + return bitmap; + } + + public static Bitmap AssertColorAtCenter(this AView view, AColor expectedColor) + { + var bitmap = view.ToBitmap(); + return bitmap.AssertColorAtCenter(expectedColor); + } + + public static Bitmap AssertColorAtBottomLeft(this AView view, AColor expectedColor) + { + var bitmap = view.ToBitmap(); + return bitmap.AssertColorAtBottomLeft(expectedColor); + } + + public static Bitmap AssertColorAtBottomRight(this AView view, AColor expectedColor) + { + var bitmap = view.ToBitmap(); + return bitmap.AssertColorAtBottomRight(expectedColor); + } + + public static Bitmap AssertColorAtTopLeft(this AView view, AColor expectedColor) + { + var bitmap = view.ToBitmap(); + return bitmap.AssertColorAtTopLeft(expectedColor); + } + + public static Bitmap AssertColorAtTopRight(this AView view, AColor expectedColor) + { + var bitmap = view.ToBitmap(); + return bitmap.AssertColorAtTopRight(expectedColor); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/BackgroundColorTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/BackgroundColorTests.cs new file mode 100644 index 00000000000..faa61e107ea --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Tests/BackgroundColorTests.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Android.Graphics.Drawables; +using NUnit.Framework; +using NUnit.Framework.Internal; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Platform.Android; + +namespace Xamarin.Forms.ControlGallery.Android.Tests +{ + [TestFixture] + public class BackgroundColorTests : PlatformTestFixture + { + static IEnumerable TestCases + { + get + { + foreach (var element in BasicElements + .Where(e => !(e is Button) && !(e is ImageButton) && !(e is Frame))) + { + element.BackgroundColor = Color.AliceBlue; + yield return new TestCaseData(element) + .SetCategory(element.GetType().Name); + } + } + } + + [Test, Category("BackgroundColor"), Category("Button")] + [Description("Button background color should match renderer background color")] + public void ButtonBackgroundColorConsistent() + { + var button = new Button + { + Text = " ", + HeightRequest = 100, WidthRequest = 100, + BackgroundColor = Color.AliceBlue + }; + + using (var nativeButton = GetNativeControl(button)) + { + var expectedColor = button.BackgroundColor.ToAndroid(); + Layout(button, nativeButton); + nativeButton.AssertColorAtCenter(expectedColor); + } + } + + [Test, Category("BackgroundColor"), Category("Button")] + [Description("ImageButton background color should match renderer background color")] + public void ImageButtonBackgroundColorConsistent() + { + var button = new ImageButton + { + HeightRequest = 100, + WidthRequest = 100, + BackgroundColor = Color.AliceBlue + }; + + using (var nativeButton = GetNativeControl(button)) + { + var expectedColor = button.BackgroundColor.ToAndroid(); + Layout(button, nativeButton); + nativeButton.AssertColorAtCenter(expectedColor); + } + } + + [Test, Category("BackgroundColor")] + [Description("Frame background color should match renderer background color")] + public void FrameBackgroundColorConsistent() + { + var frame = new Frame + { + HeightRequest = 100, + WidthRequest = 100, + BackgroundColor = Color.AliceBlue + }; + + using (var renderer = GetRenderer(frame)) + { + var expectedColor = frame.BackgroundColor.ToAndroid(); + var view = renderer.View; + Layout(frame, view); + view.AssertColorAtCenter(expectedColor); + } + } + + [Test, Category("BackgroundColor"), TestCaseSource(nameof(TestCases))] + [Description("VisualElement background color should match renderer background color")] + public void BackgroundColorConsistent(VisualElement element) + { + using (var renderer = GetRenderer(element)) + { + var expectedColor = element.BackgroundColor.ToAndroid(); + var view = renderer.View; + var colorDrawable = view.Background as ColorDrawable; + var nativeColor = colorDrawable.Color; + Assert.That(nativeColor, Is.EqualTo(expectedColor)); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/CornerRadiusTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/CornerRadiusTests.cs new file mode 100644 index 00000000000..65f02b21e1a --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Tests/CornerRadiusTests.cs @@ -0,0 +1,96 @@ +using NUnit.Framework; +using NUnit.Framework.Internal; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Platform.Android; + +namespace Xamarin.Forms.ControlGallery.Android.Tests +{ + [TestFixture] + public class CornerRadiusTests : PlatformTestFixture + { + [Test, Category("CornerRadius"), Category("BoxView")] + public void BoxviewCornerRadius() + { + var boxView = new BoxView + { + HeightRequest = 100, + WidthRequest = 200, + CornerRadius = 15, + BackgroundColor = Color.Red + }; + + CheckCornerRadius(boxView); + } + + [Test, Category("CornerRadius"), Category("Button")] + public void ButtonCornerRadius() + { + var backgroundColor = Color.Red; + + var button = new Button + { + HeightRequest = 100, + WidthRequest = 200, + CornerRadius = 15, + BackgroundColor = backgroundColor + }; + + CheckCornerRadius(button); + } + + [Test, Category("CornerRadius"), Category("Frame")] + public void FrameCornerRadius() + { + var backgroundColor = Color.Red; + + var frame = new Frame + { + HeightRequest = 100, + WidthRequest = 200, + CornerRadius = 15, + BackgroundColor = backgroundColor + }; + + CheckCornerRadius(frame); + } + + [Test, Category("CornerRadius"), Category("ImageButton")] + public void ImageButtonCornerRadius() + { + var backgroundColor = Color.Red; + + var button = new ImageButton + { + HeightRequest = 100, + WidthRequest = 200, + CornerRadius = 15, + BackgroundColor = backgroundColor, + BorderColor = Color.Black, + BorderWidth = 2 + }; + + CheckCornerRadius(button); + } + + public void CheckCornerRadius(VisualElement visualElement) + { + using (var renderer = GetRenderer(visualElement)) + { + var view = renderer.View; + Layout(visualElement, view); + + // Need to parent the Frame for it to work on lower APIs (below Marshmallow) + ParentView(view); + + // The corners should show the background color + view.AssertColorAtTopLeft(EmptyBackground) + .AssertColorAtTopRight(EmptyBackground) + .AssertColorAtBottomLeft(EmptyBackground) + .AssertColorAtBottomRight(EmptyBackground) + .AssertColorAtCenter(visualElement.BackgroundColor.ToAndroid()); + + UnparentView(view); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/IsEnabledTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/IsEnabledTests.cs new file mode 100644 index 00000000000..83e303b2304 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Tests/IsEnabledTests.cs @@ -0,0 +1,55 @@ +using System.Collections; +using NUnit.Framework; +using NUnit.Framework.Internal; +using Xamarin.Forms.CustomAttributes; + +namespace Xamarin.Forms.ControlGallery.Android.Tests +{ + [TestFixture] + public class IsEnabledTests : PlatformTestFixture + { + static IEnumerable TestCases + { + get + { + // Generate IsEnabled = true cases + foreach (var element in BasicElements) + { + element.IsEnabled = true; + yield return CreateTestCase(element) + .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}"); + } + + // Generate IsEnabled = false cases + foreach (var element in BasicElements) + { + element.IsEnabled = false; + yield return CreateTestCase(element) + .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}"); + } + } + } + + [Test, Category("IsEnabled"), TestCaseSource(nameof(TestCases))] + [Description("VisualElement enabled should match renderer enabled")] + public void EnabledConsistent(VisualElement element) + { + using (var renderer = GetRenderer(element)) + { + var expected = element.IsEnabled; + var nativeView = renderer.View; + + ParentView(nativeView); + + // Check the container control + Assert.That(renderer.View.Enabled, Is.EqualTo(expected)); + + // Check the actual control + var control = GetNativeControl(element); + Assert.That(control.Enabled, Is.EqualTo(expected)); + + UnparentView(nativeView); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/IsVisibleTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/IsVisibleTests.cs new file mode 100644 index 00000000000..41f65a4384f --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Tests/IsVisibleTests.cs @@ -0,0 +1,53 @@ +using System.Collections; +using NUnit.Framework; +using NUnit.Framework.Internal; +using Xamarin.Forms.CustomAttributes; + +namespace Xamarin.Forms.ControlGallery.Android.Tests +{ + [TestFixture] + public class IsVisibleTests : PlatformTestFixture + { + static IEnumerable TestCases + { + get + { + // Generate IsVisible = true cases + foreach (var element in BasicElements) + { + element.IsVisible = true; + yield return CreateTestCase(element) + .SetName($"{element.GetType().Name}_IsVisible_True"); + } + + // Generate IsVisible = false cases + foreach (var element in BasicElements) + { + element.IsVisible = false; + yield return CreateTestCase(element) + .SetName($"{element.GetType().Name}_IsVisible_False"); + } + } + } + + [Test, Category("IsVisible"), TestCaseSource(nameof(TestCases))] + [Description("VisualElement visibility should match renderer visibility")] + public void VisibleConsistent(VisualElement element) + { + using (var renderer = GetRenderer(element)) + { + var expected = element.IsVisible + ? global::Android.Views.ViewStates.Visible + : global::Android.Views.ViewStates.Invisible; + + var nativeView = renderer.View; + + ParentView(nativeView); + + Assert.That(renderer.View.Visibility, Is.EqualTo(expected)); + + UnparentView(nativeView); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/Issues.cs b/Xamarin.Forms.ControlGallery.Android/Tests/Issues.cs index f52de95ac98..2b5f062d384 100644 --- a/Xamarin.Forms.ControlGallery.Android/Tests/Issues.cs +++ b/Xamarin.Forms.ControlGallery.Android/Tests/Issues.cs @@ -10,7 +10,8 @@ namespace Xamarin.Forms.ControlGallery.Android.Tests [TestFixture] public class Issues : PlatformTestFixture { - [Test(Description = "The HorizontalAlignment of an Entry's renderer should match the Entry")] + [Test, Category("Entry")] + [Description("The HorizontalAlignment of an Entry's renderer should match the Entry")] [Issue(IssueTracker.Github, 8137, "[Bug] XF 4.3 Entry HorizontalTextAlignment display wrong position")] public void EntryHorizontalAlignmentCenterInRenderer() { diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/OpacityTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/OpacityTests.cs new file mode 100644 index 00000000000..f4f8c0df36b --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Tests/OpacityTests.cs @@ -0,0 +1,40 @@ +using System.Collections; +using NUnit.Framework; +using NUnit.Framework.Internal; +using Xamarin.Forms.CustomAttributes; + +namespace Xamarin.Forms.ControlGallery.Android.Tests +{ + [TestFixture] + public class OpacityTests : PlatformTestFixture + { + static IEnumerable TestCases + { + get + { + foreach (var element in BasicElements) + { + element.Opacity = 0.35; + yield return CreateTestCase(element); + } + } + } + + [Test, Category("Opacity"), TestCaseSource(nameof(TestCases))] + [Description("VisualElement opacity should match renderer opacity")] + public void OpacityConsistent(VisualElement element) + { + using (var renderer = GetRenderer(element)) + { + var expected = element.Opacity; + var nativeView = renderer.View; + + ParentView(nativeView); + + Assert.That((double)renderer.View.Alpha, Is.EqualTo(expected).Within(0.001d)); + + UnparentView(nativeView); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestFixture.cs b/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestFixture.cs index 2d61e91efb2..febada0d9e5 100644 --- a/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestFixture.cs +++ b/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestFixture.cs @@ -2,6 +2,21 @@ using Android.Content.PM; using Android.Widget; using Xamarin.Forms.Platform.Android; +using AView = Android.Views.View; +using AColor = Android.Graphics.Color; +using Android.Views; +using System; +using AProgressBar = Android.Widget.ProgressBar; +using ASearchView = Android.Widget.SearchView; +using System.Collections.Generic; +using NUnit.Framework; + +#if __ANDROID_29__ +using AndroidX.AppCompat.Widget; +using AndroidX.CardView.Widget; +#else +using Android.Support.V7.Widget; +#endif namespace Xamarin.Forms.ControlGallery.Android.Tests { @@ -9,6 +24,42 @@ public class PlatformTestFixture { Context _context; + protected static AColor EmptyBackground = new AColor(0, 0, 0, 255); + + // Sequence for generating test cases + protected static IEnumerable BasicElements + { + get + { + yield return new BoxView { }; + yield return new Button { }; + yield return new CheckBox { }; + yield return new DatePicker { }; + yield return new Editor { }; + yield return new Entry { }; + yield return new Frame { }; + yield return new Image { }; + yield return new ImageButton { }; + yield return new Label { }; + yield return new Picker { }; + yield return new ProgressBar { }; + yield return new SearchBar { }; + yield return new Slider { }; + yield return new Stepper { }; + yield return new Switch { }; + yield return new TimePicker { }; + } + } + + protected static TestCaseData CreateTestCase(VisualElement element) + { + // We set the element type as a category on the test so that if you + // filter by category, say, "Button", you'll get any Button test + // generated from here. + + return new TestCaseData(element).SetCategory(element.GetType().Name); + } + protected Context Context { get @@ -31,21 +82,217 @@ protected static void ToggleRTLSupport(Context context, bool enabled) protected IVisualElementRenderer GetRenderer(VisualElement element) { - return Platform.Android.Platform.CreateRendererWithContext(element, Context); + var renderer = element.GetRenderer(); + if (renderer == null) + { + renderer = Platform.Android.Platform.CreateRendererWithContext(element, Context); + Platform.Android.Platform.SetRenderer(element, renderer); + } + + return renderer; + } + + protected AView GetNativeControl(VisualElement element) + { + switch (element) + { + case BoxView boxView: + return GetNativeControl(boxView); + case Button button: + return GetNativeControl(button); + case CheckBox checkBox: + return GetNativeControl(checkBox); + case DatePicker datePicker: + return GetNativeControl(datePicker); + case Editor editor: + return GetNativeControl(editor); + case Entry entry: + return GetNativeControl(entry); + case Image image: + return GetNativeControl(image); + case ImageButton imageButton: + return GetNativeControl(imageButton); + case Frame frame: + return GetNativeControl(frame); + case Label label: + return GetNativeControl(label); + case Picker picker: + return GetNativeControl(picker); + case ProgressBar progressBar: + return GetNativeControl(progressBar); + case SearchBar searchBar: + return GetNativeControl(searchBar); + case Slider slider: + return GetNativeControl(slider); + case Stepper stepper: + return GetNativeControl(stepper); + case Switch @switch: + return GetNativeControl(@switch); + case TimePicker timePicker: + return GetNativeControl(timePicker); + } + + throw new NotImplementedException($"Don't know how to get the native control for {element}"); } - protected TextView GetNativeControl(Label label) + protected BoxRenderer GetNativeControl(BoxView boxView) { - var renderer = GetRenderer(label); - var viewRenderer = renderer.View as ViewRenderer; + var renderer = GetRenderer(boxView); + return renderer as BoxRenderer; + } + + protected AppCompatButton GetNativeControl(Button button) + { + var renderer = GetRenderer(button); + + if (renderer is AppCompatButton fastButton) + { + return fastButton; + } + + var viewRenderer = renderer.View as Platform.Android.AppCompat.ButtonRenderer; + return viewRenderer.Control; + } + + protected AppCompatCheckBox GetNativeControl(CheckBox checkbox) + { + var renderer = GetRenderer(checkbox); + return renderer as AppCompatCheckBox; + } + + protected EditText GetNativeControl(DatePicker datePicker) + { + var renderer = GetRenderer(datePicker); + var viewRenderer = renderer.View as DatePickerRenderer; + return viewRenderer.Control; + } + + protected FormsEditText GetNativeControl(Editor editor) + { + var renderer = GetRenderer(editor); + var viewRenderer = renderer.View as EditorRenderer; return viewRenderer.Control; } protected FormsEditText GetNativeControl(Entry entry) { var renderer = GetRenderer(entry); - var viewRenderer = renderer.View as ViewRenderer; + var viewRenderer = renderer.View as EntryRenderer; return viewRenderer.Control; } + + protected ImageView GetNativeControl(Image image) + { + var renderer = GetRenderer(image); + + if (renderer is ImageView fastImage) + { + return fastImage; + } + + var viewRenderer = renderer.View as ImageRenderer; + return viewRenderer.Control; + } + + protected AppCompatImageButton GetNativeControl(ImageButton imageButton) + { + return GetRenderer(imageButton) as AppCompatImageButton; + } + + protected CardView GetNativeControl(Frame frame) + { + var renderer = GetRenderer(frame); + return renderer as CardView; + } + + protected TextView GetNativeControl(Label label) + { + var renderer = GetRenderer(label); + var viewRenderer = renderer.View as LabelRenderer; + + if (viewRenderer != null) + { + return viewRenderer.Control; + } + + var fastRenderer = renderer.View as Platform.Android.FastRenderers.LabelRenderer; + + return fastRenderer; + } + + protected EditText GetNativeControl(Picker picker) + { + var renderer = GetRenderer(picker); + var viewRenderer = renderer.View as Platform.Android.AppCompat.PickerRenderer; + return viewRenderer.Control; + } + + protected AProgressBar GetNativeControl(ProgressBar progressBar) + { + var renderer = GetRenderer(progressBar); + var viewRenderer = renderer.View as ProgressBarRenderer; + return viewRenderer.Control; + } + + protected ASearchView GetNativeControl(SearchBar searchBar) + { + var renderer = GetRenderer(searchBar); + var viewRenderer = renderer.View as SearchBarRenderer; + return viewRenderer.Control; + } + + protected SeekBar GetNativeControl(Slider slider) + { + var renderer = GetRenderer(slider); + var viewRenderer = renderer.View as SliderRenderer; + return viewRenderer.Control; + } + + protected LinearLayout GetNativeControl(Stepper stepper) + { + var renderer = GetRenderer(stepper); + var viewRenderer = renderer.View as StepperRenderer; + return viewRenderer.Control; + } + + protected SwitchCompat GetNativeControl(Switch @switch) + { + var renderer = GetRenderer(@switch); + var viewRenderer = renderer.View as Platform.Android.AppCompat.SwitchRenderer; + return viewRenderer.Control; + } + + protected EditText GetNativeControl(TimePicker timePicker) + { + var renderer = GetRenderer(timePicker); + var viewRenderer = renderer.View as TimePickerRenderer; + return viewRenderer.Control; + } + + protected void Layout(VisualElement element, AView nativeView) + { + var size = element.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins); + var width = size.Request.Width; + var height = size.Request.Height; + element.Layout(new Rectangle(0, 0, width, height)); + + int widthSpec = AView.MeasureSpec.MakeMeasureSpec((int)width, MeasureSpecMode.Exactly); + int heightSpec = AView.MeasureSpec.MakeMeasureSpec((int)height, MeasureSpecMode.Exactly); + nativeView.Measure(widthSpec, heightSpec); + nativeView.Layout(0, 0, (int)width, (int)height); + } + + // Some of the renderer properties aren't set until the renderer is actually + // attached to the view hierarchy; to test them, we need to add them, run the test, + // then remove them + protected void ParentView(AView view) + { + ((ViewGroup)Application.Current.MainPage.GetRenderer().View).AddView(view); + } + + protected void UnparentView(AView view) + { + ((ViewGroup)Application.Current.MainPage.GetRenderer().View).RemoveView(view); + } } } \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestSettings.cs b/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestSettings.cs index 0795131b48b..4faa6dc1bb6 100644 --- a/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestSettings.cs +++ b/Xamarin.Forms.ControlGallery.Android/Tests/PlatformTestSettings.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Reflection; +using NUnit; using Xamarin.Forms; using Xamarin.Forms.ControlGallery.Android.Tests; using Xamarin.Forms.Controls.Tests; @@ -9,7 +10,17 @@ namespace Xamarin.Forms.ControlGallery.Android.Tests { public class PlatformTestSettings : IPlatformTestSettings { + public PlatformTestSettings() + { + TestRunSettings = new Dictionary + { + // Creating/modifying any renderers off the UI thread causes problems + // so we want to force the tests to run on main + { FrameworkPackageSettings.RunOnMainThread, true } + }; + } + public Assembly Assembly { get => Assembly.GetExecutingAssembly(); } - public Dictionary TestRunSettings { get => new Dictionary(); } + public Dictionary TestRunSettings { get; } } } \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/RendererTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/RendererTests.cs index 19dbe8bfc2a..95482abdcf2 100644 --- a/Xamarin.Forms.ControlGallery.Android/Tests/RendererTests.cs +++ b/Xamarin.Forms.ControlGallery.Android/Tests/RendererTests.cs @@ -8,17 +8,8 @@ namespace Xamarin.Forms.ControlGallery.Android.Tests [TestFixture] public class RendererTests : PlatformTestFixture { - [Test(Description = "Basic sanity check that Label text matches renderer text")] - public void LabelTextMatchesRendererText() - { - var label = new Label { Text = "foo" }; - using (var textView = GetNativeControl(label)) - { - Assert.That(label.Text == textView.Text); - } - } - - [Test(Description = "Validate renderer vertical alignment for Entry with VerticalTextAlignment Center")] + [Test, Category("Entry")] + [Description("Validate renderer vertical alignment for Entry with VerticalTextAlignment Center")] public void EntryVerticalAlignmentCenterInRenderer() { var entry1 = new Entry { Text = "foo", VerticalTextAlignment = TextAlignment.Center }; diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/RotationTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/RotationTests.cs new file mode 100644 index 00000000000..acc27045cab --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Tests/RotationTests.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections; +using NUnit.Framework; +using NUnit.Framework.Internal; +using Xamarin.Forms.CustomAttributes; +using AView = Android.Views.View; + +namespace Xamarin.Forms.ControlGallery.Android.Tests +{ + [TestFixture] + public class RotationTests : PlatformTestFixture + { + static IEnumerable RotationXCases + { + get + { + foreach (var element in BasicElements) + { + element.RotationX = 33.0; + yield return CreateTestCase(element); + } + } + } + + static IEnumerable RotationYCases + { + get + { + foreach (var element in BasicElements) + { + element.RotationY = 87.0; + yield return CreateTestCase(element); + } + } + } + + static IEnumerable RotationCases + { + get + { + foreach (var element in BasicElements) + { + element.Rotation = 23.0; + yield return CreateTestCase(element); + } + } + } + + void AssertRotationConsistent(VisualElement element, Func getRotation, + Func getNativeRotation) + { + using (var renderer = GetRenderer(element)) + { + var expected = getRotation(element); + var nativeView = renderer.View; + + ParentView(nativeView); + + var actual = getNativeRotation(renderer.View); + + Assert.That((double)actual, Is.EqualTo(expected).Within(0.0001d)); + + UnparentView(nativeView); + } + } + + [Test, Category("RotationX"), TestCaseSource(nameof(RotationXCases))] + [Description("VisualElement X rotation should match renderer X rotation")] + public void RotationXConsistent(VisualElement element) + { + AssertRotationConsistent(element, e => e.RotationX, v => v.RotationX); + } + + [Test, Category("RotationY"), TestCaseSource(nameof(RotationYCases))] + [Description("VisualElement Y rotation should match renderer Y rotation")] + public void RotationYConsistent(VisualElement element) + { + AssertRotationConsistent(element, e => e.RotationY, v => v.RotationY); + } + + [Test, Category("Rotation"), TestCaseSource(nameof(RotationCases))] + [Description("VisualElement rotation should match renderer rotation")] + public void RotationConsistent(VisualElement element) + { + AssertRotationConsistent(element, e => e.Rotation, v => v.Rotation); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/ScaleTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/ScaleTests.cs new file mode 100644 index 00000000000..823d77a22a5 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Tests/ScaleTests.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections; +using NUnit.Framework; +using AView = Android.Views.View; + +namespace Xamarin.Forms.ControlGallery.Android.Tests +{ + [TestFixture] + public class ScaleTests : PlatformTestFixture + { + static IEnumerable ScaleXCases + { + get + { + foreach (var element in BasicElements) + { + element.ScaleX = 0.45; + yield return CreateTestCase(element); + } + } + } + + static IEnumerable ScaleYCases + { + get + { + foreach (var element in BasicElements) + { + element.ScaleY = 1.23; + yield return CreateTestCase(element); + } + } + } + + void AssertScaleConsistent(View view, Func getScale, + Func getNativeScale) + { + using (var renderer = GetRenderer(view)) + { + var expected = getScale(view); + var nativeView = renderer.View; + + ParentView(nativeView); + + Assert.That(getNativeScale(nativeView), Is.EqualTo(expected).Within(0.01)); + + UnparentView(nativeView); + } + } + + [Test, Category("ScaleX"), TestCaseSource(nameof(ScaleXCases))] + [Description("View X scale should match renderer X scale")] + public void ScaleXConsistent(View view) + { + AssertScaleConsistent(view, e => e.ScaleX, v => v.ScaleX); + } + + [Test, Category("ScaleY"), TestCaseSource(nameof(ScaleYCases))] + [Description("View Y scale should match renderer Y scale")] + public void ScaleYConsistent(View view) + { + AssertScaleConsistent(view, e => e.ScaleY, v => v.ScaleY); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/TestingPlatformService.cs b/Xamarin.Forms.ControlGallery.Android/Tests/TestingPlatformService.cs new file mode 100644 index 00000000000..f18b0301e85 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Tests/TestingPlatformService.cs @@ -0,0 +1,17 @@ +using Android.Content; +using Xamarin.Forms; +using Xamarin.Forms.ControlGallery.Android.Tests; +using Xamarin.Forms.Controls.Tests; + +[assembly: Dependency(typeof(TestingPlatformService))] +namespace Xamarin.Forms.ControlGallery.Android.Tests +{ + class TestingPlatformService : ITestingPlatformService + { + public void CreateRenderer(VisualElement visualElement) + { + Platform.Android.Platform.CreateRendererWithContext(visualElement, + DependencyService.Resolve()); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/TextTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/TextTests.cs new file mode 100644 index 00000000000..5d5ba7239a2 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Tests/TextTests.cs @@ -0,0 +1,54 @@ +using NUnit.Framework; +using NUnit.Framework.Internal; +using Xamarin.Forms.CustomAttributes; + +namespace Xamarin.Forms.ControlGallery.Android.Tests +{ + [TestFixture] + public class TextTests : PlatformTestFixture + { + [Test, Category("Text"), Category("Label")] + [Description("Label text should match renderer text")] + public void LabelTextMatchesRendererText() + { + var label = new Label { Text = "foo" }; + using (var uiLabel = GetNativeControl(label)) + { + Assert.That(label.Text, Is.EqualTo(uiLabel.Text)); + } + } + + [Test, Category("Text"), Category("Button")] + [Description("Button text should match renderer text")] + public void ButtonTextMatchesRendererText() + { + var button = new Button { Text = "foo" }; + using (var nativeControl = GetNativeControl(button)) + { + Assert.That(nativeControl.Text, Is.EqualTo(button.Text)); + } + } + + [Test, Category("Text"), Category("Entry")] + [Description("Entry text should match renderer text")] + public void EntryTextMatchesRendererText() + { + var entry = new Entry { Text = "foo" }; + using (var nativeControl = GetNativeControl(entry)) + { + Assert.That(nativeControl.Text, Is.EqualTo(entry.Text)); + } + } + + [Test, Category("Text"), Category("Editor")] + [Description("Editor text should match renderer text")] + public void EditorTextMatchesRendererText() + { + var editor = new Editor { Text = "foo" }; + using (var nativeControl = GetNativeControl(editor)) + { + Assert.That(nativeControl.Text, Is.EqualTo(editor.Text)); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Tests/TranslationTests.cs b/Xamarin.Forms.ControlGallery.Android/Tests/TranslationTests.cs new file mode 100644 index 00000000000..5884312681a --- /dev/null +++ b/Xamarin.Forms.ControlGallery.Android/Tests/TranslationTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections; +using NUnit.Framework; +using AView = Android.Views.View; +using Xamarin.Forms.Platform.Android; + +namespace Xamarin.Forms.ControlGallery.Android.Tests +{ + [TestFixture] + public class TranslationTests : PlatformTestFixture + { + static IEnumerable TranslationXCases + { + get + { + foreach (var element in BasicElements) + { + element.TranslationX = -100; + yield return CreateTestCase(element); + } + } + } + + static IEnumerable TranslationYCases + { + get + { + foreach (var element in BasicElements) + { + element.TranslationY = -40; + yield return CreateTestCase(element); + } + } + } + + void AssertTranslationConsistent(View view, Func getTranslation, + Func getNativeTranslation) + { + using (var renderer = GetRenderer(view)) + { + var expected = Context.ToPixels(getTranslation(view)); + var nativeView = renderer.View; + + ParentView(nativeView); + + Assert.That(getNativeTranslation(nativeView), Is.EqualTo(expected).Within(0.01)); + + UnparentView(nativeView); + } + } + + [Test, Category("TranslateX"), TestCaseSource(nameof(TranslationXCases))] + [Description("View X translation should match renderer X translation")] + public void TranslationXConsistent(View view) + { + AssertTranslationConsistent(view, e => e.TranslationX, v => v.TranslationX); + } + + [Test, Category("ScaleY"), TestCaseSource(nameof(TranslationYCases))] + [Description("View Y translation should match renderer Y translation")] + public void TranslationYConsistent(View view) + { + AssertTranslationConsistent(view, e => e.TranslationY, v => v.TranslationY); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj index ae7fe68333f..ee78d08929c 100644 --- a/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj +++ b/Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj @@ -1,4 +1,4 @@ - + @@ -55,7 +55,6 @@ 4 None true - false armeabi-v7a;x86 Xamarin True @@ -108,7 +107,16 @@ + + + + + + + + + @@ -122,6 +130,8 @@ + + @@ -132,6 +142,7 @@ + GalleryPages\AdvancedOpenGLGallery.cs @@ -395,6 +406,18 @@ + + + + + + + + + + + + @@ -403,4 +426,4 @@ - + \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs index bbf6ec9b163..0cc317d10b8 100644 --- a/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/CustomRenderers.cs @@ -119,7 +119,7 @@ public static void SetKeyboardFlags(this FormsTextBox Control, KeyboardFlags? fl } - public class TextBoxViewRenderer : BoxViewRenderer + public class TextBoxViewRenderer : BoxViewBorderRenderer { Canvas m_Canvas; diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs index 8651729792b..63c56eb4f20 100644 --- a/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/MainPage.xaml.cs @@ -60,6 +60,10 @@ void OnKeyDown(CoreWindow coreWindow, KeyEventArgs args) _app.Reset(); args.Handled = true; } + else if (args.VirtualKey == VirtualKey.F1) + { + _app.PlatformTest(); + } } void AddNativeControls(NestedNativeControlGalleryPage page) diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/BackgroundColorTests.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/BackgroundColorTests.cs new file mode 100644 index 00000000000..9cda58458c6 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/BackgroundColorTests.cs @@ -0,0 +1,100 @@ +using System.Collections; +using System.Linq; +using NUnit.Framework; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Shapes; +using Xamarin.Forms.Platform.UWP; +using WColor = Windows.UI.Color; + +namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests +{ + [TestFixture] + public class BackgroundColorTests : PlatformTestFixture + { + static IEnumerable TestCases + { + get + { + // SearchBar is currently busted; when 8773 gets merged we can stop filtering it + foreach (var element in BasicViews + .Where(v => !(v is SearchBar)) + .Where(v => !(v is Frame))) + { + element.BackgroundColor = Color.AliceBlue; + yield return CreateTestCase(element); + } + } + } + + WColor GetBackgroundColor(Control control) + { + if (control is FormsButton button) + { + return (button.BackgroundColor as SolidColorBrush).Color; + } + + if (control is StepperControl stepper) + { + return stepper.ButtonBackgroundColor.ToUwpColor(); + } + + return (control.Background as SolidColorBrush).Color; + } + + WColor GetBackgroundColor(Panel panel) + { + return (panel.Background as SolidColorBrush).Color; + } + + WColor GetBackgroundColor(Border border) + { + return (border.Background as SolidColorBrush).Color; + } + + WColor GetNativeColor(View view) + { + var control = GetNativeControl(view); + + if (control != null) + { + return GetBackgroundColor(control); + } + + var border = GetBorder(view); + + if (border != null) + { + return GetBackgroundColor(border); + } + + var panel = GetPanel(view); + return GetBackgroundColor(panel); + } + + [Test, TestCaseSource(nameof(TestCases))] + [Description("View background color should match renderer background color")] + public void BackgroundColorConsistent(View view) + { + var nativeColor = GetNativeColor(view); + var formsColor = view.BackgroundColor.ToUwpColor(); + Assert.That(nativeColor, Is.EqualTo(formsColor)); + } + + [Test, Category("BackgroundColor"), Category("Frame")] + [Description("Frame background color should match renderer background color")] + public void FrameBackgroundColorConsistent() + { + var frame = new Frame() { BackgroundColor = Color.Orange }; + + var renderer = GetRenderer(frame); + var nativeElement = renderer.GetNativeElement() as Border; + + var backgroundBrush = nativeElement.Background as SolidColorBrush; + var actualColor = backgroundBrush.Color; + + var expectedColor = frame.BackgroundColor.ToUwpColor(); + Assert.That(actualColor, Is.EqualTo(expectedColor)); + } + } +} diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/IsEnabledTests.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/IsEnabledTests.cs new file mode 100644 index 00000000000..e100827fe84 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/IsEnabledTests.cs @@ -0,0 +1,54 @@ +using System.Collections; +using NUnit.Framework; + +namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests +{ + [TestFixture] + public class IsEnabledTests : PlatformTestFixture + { + static IEnumerable TestCases + { + get + { + // Generate IsEnabled = true cases + foreach (var element in BasicViews) + { + element.IsEnabled = true; + yield return CreateTestCase(element) + .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}"); + } + + // Generate IsEnabled = false cases + foreach (var element in BasicViews) + { + element.IsEnabled = false; + yield return CreateTestCase(element) + .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}"); + } + } + } + + [Test, Category("IsEnabled"), TestCaseSource(nameof(TestCases))] + [Description("View enabled should match renderer enabled")] + public void EnabledConsistent(VisualElement element) + { + using (var renderer = GetRenderer(element)) + { + var expected = element.IsEnabled; + var container = renderer.ContainerElement; + + // Check the container control + Assert.That(container.IsHitTestVisible, Is.EqualTo(expected)); + + // Check the actual control (if there is one; for some renderers, like Frame and BoxView, the + // native control isn't a Windows.UI.Xaml.Controls.Control) + var control = GetNativeControl(element); + + if (control != null) + { + Assert.That(control.IsEnabled, Is.EqualTo(expected)); + } + } + } + } +} diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestFixture.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestFixture.cs new file mode 100644 index 00000000000..7a12b328c62 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestFixture.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using NUnit.Framework; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Shapes; +using Xamarin.Forms.Platform.UWP; + +namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests +{ + public class PlatformTestFixture + { + // Sequence for generating test cases + protected static IEnumerable BasicViews + { + get + { + yield return new BoxView { }; + yield return new Button { }; + yield return new CheckBox { }; + yield return new DatePicker { }; + yield return new Editor { }; + yield return new Entry { }; + yield return new Frame { }; + yield return new Image { }; + yield return new ImageButton { }; + yield return new Label { }; + yield return new Picker { }; + yield return new ProgressBar { }; + yield return new SearchBar { }; + yield return new Slider { }; + yield return new Stepper { }; + yield return new Switch { }; + yield return new TimePicker { }; + } + } + + protected static TestCaseData CreateTestCase(VisualElement element) + { + // We set the element type as a category on the test so that if you + // filter by category, say, "Button", you'll get any Button test + // generated from here. + + return new TestCaseData(element).SetCategory(element.GetType().Name); + } + + protected IVisualElementRenderer GetRenderer(VisualElement element) + { + return element.GetOrCreateRenderer(); + } + + protected Control GetNativeControl(VisualElement element) + { + return GetRenderer(element).GetNativeElement() as Control; + } + + protected Panel GetPanel(VisualElement element) + { + return GetRenderer(element).ContainerElement as Panel; + } + + protected Border GetBorder(VisualElement element) + { + var renderer = GetRenderer(element); + var nativeElement = renderer.GetNativeElement(); + return nativeElement as Border; + } + + protected TextBlock GetNativeControl(Label label) + { + return GetRenderer(label).GetNativeElement() as TextBlock; + } + + protected FormsButton GetNativeControl(Button button) + { + return GetRenderer(button).GetNativeElement() as FormsButton; + } + + protected FormsTextBox GetNativeControl(Entry entry) + { + return GetRenderer(entry).GetNativeElement() as FormsTextBox; + } + + protected FormsTextBox GetNativeControl(Editor editor) + { + return GetRenderer(editor).GetNativeElement() as FormsTextBox; + } + } +} diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestSettings.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestSettings.cs new file mode 100644 index 00000000000..aa93e5bf024 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/PlatformTestSettings.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Reflection; +using NUnit; +using Xamarin.Forms; +using Xamarin.Forms.ControlGallery.WindowsUniversal.Tests; +using Xamarin.Forms.Controls.Tests; + +[assembly: Dependency(typeof(PlatformTestSettings))] +namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests +{ + public class PlatformTestSettings : IPlatformTestSettings + { + public PlatformTestSettings() + { + TestRunSettings = new Dictionary + { + // Creating/modifying any renderers off the UI thread causes problems + // so we want to force the tests to run on main + { FrameworkPackageSettings.RunOnMainThread, true } + }; + } + + public Assembly Assembly { get => Assembly.GetExecutingAssembly(); } + public Dictionary TestRunSettings { get; } + } +} diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RendererTests.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RendererTests.cs new file mode 100644 index 00000000000..7f49594220f --- /dev/null +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RendererTests.cs @@ -0,0 +1,17 @@ +using NUnit.Framework; + +namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests +{ + [TestFixture] + public class RendererTests : PlatformTestFixture + { + [Test] + [Description("Basic sanity check that Label text matches renderer text")] + public void LabelTextMatchesRendererText() + { + var label = new Label { Text = "foo" }; + var textBlock = GetNativeControl(label); + Assert.That(label.Text == textBlock.Text); + } + } +} diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RotationTests.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RotationTests.cs new file mode 100644 index 00000000000..dbaa4f47398 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/RotationTests.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections; +using NUnit.Framework; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; + +namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests +{ + [TestFixture] + public class RotationTests : PlatformTestFixture + { + static IEnumerable RotationXCases + { + get + { + foreach (var element in BasicViews) + { + element.RotationX = 33.0; + yield return CreateTestCase(element); + } + } + } + + static IEnumerable RotationYCases + { + get + { + foreach (var element in BasicViews) + { + element.RotationY = 86.0; + yield return CreateTestCase(element); + } + } + } + + static IEnumerable RotationCases + { + get + { + foreach (var element in BasicViews) + { + element.Rotation = 23.0; + yield return CreateTestCase(element); + } + } + } + + void AssertRotationConsistent(View view, Func getRotation, + Func getNativeRotation) + { + var frameworkElement = GetRenderer(view).ContainerElement; + + var expected = getRotation(view); + var actual = getNativeRotation(frameworkElement); + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test, Category("RotationX"), TestCaseSource(nameof(RotationXCases))] + [Description("View X rotation should match renderer X rotation")] + public void RotationXConsistent(View view) + { + AssertRotationConsistent(view, + e => e.RotationX, GetRotationX); + } + + [Test, Category("RotationY"), TestCaseSource(nameof(RotationYCases))] + [Description("View Y rotation should match renderer Y rotation")] + public void RotationYConsistent(View view) + { + AssertRotationConsistent(view, + e => e.RotationY, GetRotationY); + } + + [Test, Category("Rotation"), TestCaseSource(nameof(RotationCases))] + [Description("View rotation should match renderer rotation")] + public void RotationConsistent(View view) + { + AssertRotationConsistent(view, + e => e.Rotation, GetRotation); + } + + + double GetRotationX(FrameworkElement fe) + { + if (fe.Projection is PlaneProjection planeProjection) + { + return -planeProjection.RotationX; + } + + throw new Exception("No rotation found"); + } + + double GetRotationY(FrameworkElement fe) + { + if (fe.Projection is PlaneProjection planeProjection) + { + return -planeProjection.RotationY; + } + + throw new Exception("No rotation found"); + } + + double GetRotation(FrameworkElement fe) + { + if (fe.RenderTransform is CompositeTransform compositeTransform) + { + return compositeTransform.Rotation; + } + + throw new Exception("No rotation found"); + } + } +} diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/ScaleTests.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/ScaleTests.cs new file mode 100644 index 00000000000..55fc1765239 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/ScaleTests.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections; +using NUnit.Framework; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; + +namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests +{ + [TestFixture] + public class ScaleTests : PlatformTestFixture + { + static IEnumerable ScaleXCases + { + get + { + foreach (var element in BasicViews) + { + element.ScaleX = 0.45; + yield return CreateTestCase(element); + } + } + } + + static IEnumerable ScaleYCases + { + get + { + foreach (var element in BasicViews) + { + element.ScaleY = 1.23; + yield return CreateTestCase(element); + } + } + } + + static IEnumerable ScaleCases + { + get + { + foreach (var element in BasicViews) + { + element.Scale = 0.5; + yield return CreateTestCase(element); + } + } + } + + void AssertScaleConsistent(View view, Func getScale, + Func getNativeScale) + { + var frameworkElement = GetRenderer(view).ContainerElement; + + var expected = getScale(view); + var actual = getNativeScale(frameworkElement); + + Assert.That(actual, Is.EqualTo(expected).Within(0.001d)); + } + + [Test, Category("ScaleX"), TestCaseSource(nameof(ScaleXCases))] + [Description("View X scale should match renderer X scale")] + public void ScaleXConsistent(View view) + { + AssertScaleConsistent(view, e => e.ScaleX, GetScaleX); + } + + [Test, Category("ScaleY"), TestCaseSource(nameof(ScaleYCases))] + [Description("View Y scale should match renderer Y scale")] + public void ScaleYConsistent(View view) + { + AssertScaleConsistent(view, e => e.ScaleY, GetScaleY); + } + + [Test, Category("Scale"), TestCaseSource(nameof(ScaleCases))] + [Description("View scale should match renderer scale")] + public void ScaleConsistent(View view) + { + AssertScaleConsistent(view, e => e.Scale, GetScaleX); + AssertScaleConsistent(view, e => e.Scale, GetScaleY); + } + + double GetScaleX(FrameworkElement fe) + { + if (fe.RenderTransform is CompositeTransform compositeTransform) + { + return compositeTransform.ScaleX; + } + + throw new Exception("Could not determine ScaleX"); + } + + double GetScaleY(FrameworkElement fe) + { + if (fe.RenderTransform is CompositeTransform compositeTransform) + { + return compositeTransform.ScaleY; + } + + throw new Exception("Could not determine ScaleY"); + } + } +} diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/TestingPlatformService.cs b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/TestingPlatformService.cs new file mode 100644 index 00000000000..290382a2ce3 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Tests/TestingPlatformService.cs @@ -0,0 +1,15 @@ +using Xamarin.Forms; +using Xamarin.Forms.ControlGallery.WindowsUniversal.Tests; +using Xamarin.Forms.Controls.Tests; + +[assembly: Dependency(typeof(TestingPlatformService))] +namespace Xamarin.Forms.ControlGallery.WindowsUniversal.Tests +{ + class TestingPlatformService : ITestingPlatformService + { + public void CreateRenderer(VisualElement visualElement) + { + Platform.UWP.Platform.CreateRenderer(visualElement); + } + } +} diff --git a/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj b/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj index d0b625241ba..9eddad9111b 100644 --- a/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj +++ b/Xamarin.Forms.ControlGallery.WindowsUniversal/Xamarin.Forms.ControlGallery.WindowsUniversal.csproj @@ -131,6 +131,14 @@ + + + + + + + + @@ -251,6 +259,9 @@ 11.0.2 + + 3.12.0 + 3.0.2 diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/AssertionExtensions.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/AssertionExtensions.cs new file mode 100644 index 00000000000..d60f3d0a968 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/AssertionExtensions.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections; +using CoreGraphics; +using NUnit.Framework; +using NUnit.Framework.Constraints; +using UIKit; + +namespace Xamarin.Forms.ControlGallery.iOS.Tests +{ + internal static class AssertionExtensions + { + public static string CreateColorAtPointError(this UIImage bitmap, UIColor expectedColor, int x, int y) + { + var data = bitmap.AsPNG(); + var imageAsString = data.GetBase64EncodedString(Foundation.NSDataBase64EncodingOptions.None); + return $"Expected {expectedColor} at point {x},{y} in renderered view. This is what it looked like:{imageAsString}"; + } + + public static UIImage ToBitmap(this UIView view) + { + var imageRect = new CGRect(0, 0, view.Frame.Width, view.Frame.Height); + + UIGraphics.BeginImageContext(imageRect.Size); + + var context = UIGraphics.GetCurrentContext(); + view.Layer.RenderInContext(context); + var image = UIGraphics.GetImageFromCurrentImageContext(); + + UIGraphics.EndImageContext(); + + return image; + } + + public static UIColor ColorAtPoint(this UIImage bitmap, int x, int y) + { + var pixel = bitmap.GetPixel(x, y); + + // Returned pixel data is B, G, R, A (ARGB little endian byte order) + var color = new UIColor(pixel[2] / 255.0f, pixel[1] / 255.0f, pixel[0] / 255.0f, pixel[3] / 255.0f); + + return color; + } + + public static byte[] GetPixel(this UIImage bitmap, int x, int y) + { + var cgImage = bitmap.CGImage.WithColorSpace(CGColorSpace.CreateDeviceRGB()); + + // Grab the raw image data + var nsData = cgImage.DataProvider.CopyData(); + + // Copy the data into a buffer + var dataBytes = new byte[nsData.Length]; + System.Runtime.InteropServices.Marshal.Copy(nsData.Bytes, dataBytes, 0, (int)nsData.Length); + + // Figure out where the pixel we care about is + var pixelLocation = (cgImage.BytesPerRow * y) + (4 * x); + + var pixel = new byte[4] + { + dataBytes[pixelLocation], + dataBytes[pixelLocation + 1], + dataBytes[pixelLocation + 2], + dataBytes[pixelLocation + 3], + }; + + return pixel; + } + + static bool ARGBEquivalent(UIColor color1, UIColor color2) + { + color1.GetRGBA(out nfloat red1, out nfloat green1, out nfloat blue1, out nfloat alpha1); + color2.GetRGBA(out nfloat red2, out nfloat green2, out nfloat blue2, out nfloat alpha2); + + const double tolerance = 0.000001; + + return Equal(red1, red2, tolerance) + && Equal(green1, green2, tolerance) + && Equal(blue1, blue2, tolerance) + && Equal(alpha1, alpha2, tolerance); + } + + static bool Equal(nfloat v1, nfloat v2, double tolerance) + { + return Math.Abs(v1 - v2) <= tolerance; + } + + public static UIImage AssertColorAtPoint(this UIImage bitmap, UIColor expectedColor, int x, int y) + { + try + { + var cap = bitmap.ColorAtPoint(x, y); + + if (!ARGBEquivalent(cap, expectedColor)) + { + System.Diagnostics.Debug.WriteLine("Here"); + } + + Assert.That(cap, Is.EqualTo(expectedColor).Using(ARGBEquivalent), + () => bitmap.CreateColorAtPointError(expectedColor, x, y)); + + return bitmap; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex); + } + + return null; + } + + public static UIImage AssertColorAtCenter(this UIImage bitmap, UIColor expectedColor) + { + AssertColorAtPoint(bitmap, expectedColor, (int)bitmap.Size.Width / 2, (int)bitmap.Size.Height / 2); + return bitmap; + } + + public static UIImage AssertColorAtBottomLeft(this UIImage bitmap, UIColor expectedColor) + { + return bitmap.AssertColorAtPoint(expectedColor, 0, 0); + } + + public static UIImage AssertColorAtBottomRight(this UIImage bitmap, UIColor expectedColor) + { + return bitmap.AssertColorAtPoint(expectedColor, (int)bitmap.Size.Width - 1, 0); + } + + public static UIImage AssertColorAtTopLeft(this UIImage bitmap, UIColor expectedColor) + { + return bitmap.AssertColorAtPoint(expectedColor, 0, (int)bitmap.Size.Height - 1); + } + + public static UIImage AssertColorAtTopRight(this UIImage bitmap, UIColor expectedColor) + { + return bitmap.AssertColorAtPoint(expectedColor, (int)bitmap.Size.Width - 1, (int)bitmap.Size.Height - 1); + } + + public static UIImage AssertColorAtPoint(this UIView view, UIColor expectedColor, int x, int y) + { + var bitmap = view.ToBitmap(); + return bitmap.AssertColorAtPoint(expectedColor, x, y); + } + + public static UIImage AssertColorAtCenter(this UIView view, UIColor expectedColor) + { + var bitmap = view.ToBitmap(); + return bitmap.AssertColorAtCenter(expectedColor); + } + + public static UIImage AssertColorAtBottomLeft(this UIView view, UIColor expectedColor) + { + var bitmap = view.ToBitmap(); + return bitmap.AssertColorAtBottomLeft(expectedColor); + } + + public static UIImage AssertColorAtBottomRight(this UIView view, UIColor expectedColor) + { + var bitmap = view.ToBitmap(); + return bitmap.AssertColorAtBottomRight(expectedColor); + } + + public static UIImage AssertColorAtTopLeft(this UIView view, UIColor expectedColor) + { + var bitmap = view.ToBitmap(); + return bitmap.AssertColorAtTopLeft(expectedColor); + } + + public static UIImage AssertColorAtTopRight(this UIView view, UIColor expectedColor) + { + var bitmap = view.ToBitmap(); + return bitmap.AssertColorAtTopRight(expectedColor); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/BackgroundColorTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/BackgroundColorTests.cs new file mode 100644 index 00000000000..dbe9fa6ecd8 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/BackgroundColorTests.cs @@ -0,0 +1,81 @@ +using System.Collections; +using System.Linq; +using NUnit.Framework; +using UIKit; +using Xamarin.Forms.Platform.iOS; + +namespace Xamarin.Forms.ControlGallery.iOS.Tests +{ + [TestFixture] + public class BackgroundColorTests : PlatformTestFixture + { + static IEnumerable TestCases + { + get + { + foreach (var element in BasicViews + .Where(e => !(e is Label) && !(e is BoxView) && !(e is Frame))) + { + element.BackgroundColor = Color.AliceBlue; + yield return new TestCaseData(element) + .SetCategory(element.GetType().Name); + } + } + } + + [Test, Category("BackgroundColor"), TestCaseSource(nameof(TestCases))] + [Description("VisualElement background color should match renderer background color")] + public void BackgroundColorConsistent(VisualElement element) + { + using (var uiView = GetNativeControl(element)) + { + var expectedColor = element.BackgroundColor.ToUIColor(); + Assert.That(uiView.BackgroundColor, Is.EqualTo(expectedColor)); + } + } + + [Test, Category("BackgroundColor"), Category("Frame")] + [Description("Frame background color should match renderer background color")] + public void FrameBackgroundColorConsistent() + { + var frame = new Frame { BackgroundColor = Color.AliceBlue }; + using (var renderer = GetRenderer(frame)) + { + var expectedColor = frame.BackgroundColor.ToUIColor(); + Assert.That(renderer.NativeView.BackgroundColor, Is.EqualTo(expectedColor)); + } + } + + [Test, Category("BackgroundColor"), Category("Label")] + [Description("Label background color should match renderer background color")] + public void LabelBackgroundColorConsistent() + { + var label = new Label { Text = "foo", BackgroundColor = Color.AliceBlue }; + using (var renderer = GetRenderer(label)) + { + var expectedColor = label.BackgroundColor.ToUIColor(); + Assert.That(renderer.NativeView.BackgroundColor, Is.EqualTo(expectedColor)); + } + } + + [Test, Category("BackgroundColor"), Category("BoxView")] + [Description("BoxView background color should match renderer background color")] + public void BoxViewBackgroundColorConsistent() + { + var boxView = new BoxView { BackgroundColor = Color.AliceBlue }; + var expectedColor = boxView.BackgroundColor.ToUIColor(); + + var page = new ContentPage() { Content = boxView }; + + using (var pageRenderer = GetRenderer(page)) + { + using (var uiView = GetRenderer(boxView).NativeView) + { + page.Layout(new Rectangle(0, 0, 200, 200)); + + uiView.AssertColorAtCenter(expectedColor); + } + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/CornerRadiusTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/CornerRadiusTests.cs new file mode 100644 index 00000000000..c4aead97e11 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/CornerRadiusTests.cs @@ -0,0 +1,96 @@ +using NUnit.Framework; +using Xamarin.Forms.Platform.iOS; + +namespace Xamarin.Forms.ControlGallery.iOS.Tests +{ + [TestFixture] + public class CornerRadiusTests : PlatformTestFixture + { + [Test, Category("CornerRadius"), Category("BoxView")] + public void BoxviewCornerRadius() + { + var boxView = new BoxView + { + HeightRequest = 100, + WidthRequest = 200, + CornerRadius = 15, + BackgroundColor = Color.CadetBlue + }; + + CheckCornerRadius(boxView); + } + + [Test, Category("CornerRadius"), Category("Button")] + public void ButtonCornerRadius() + { + var backgroundColor = Color.CadetBlue; + + var button = new Button + { + HeightRequest = 100, + WidthRequest = 200, + CornerRadius = 15, + BackgroundColor = backgroundColor + }; + + CheckCornerRadius(button); + } + + [Test, Category("CornerRadius"), Category("Frame")] + [Ignore("Will not pass until https://github.com/xamarin/Xamarin.Forms/issues/9265 is fixed")] + public void FrameCornerRadius() + { + var backgroundColor = Color.CadetBlue; + + var frame = new Frame + { + HeightRequest = 100, + WidthRequest = 200, + CornerRadius = 15, + BackgroundColor = backgroundColor, + BorderColor = Color.Brown, + Content = new Label { Text = "Hey" } + }; + + CheckCornerRadius(frame); + } + + [Test, Category("CornerRadius"), Category("ImageButton")] + public void ImageButtonCornerRadius() + { + var backgroundColor = Color.CadetBlue; + + var button = new ImageButton + { + HeightRequest = 100, + WidthRequest = 200, + CornerRadius = 15, + BackgroundColor = backgroundColor + }; + + CheckCornerRadius(button); + } + + public void CheckCornerRadius(View view) + { + var page = new ContentPage() { Content = view }; + + using (var pageRenderer = GetRenderer(page)) + { + using (var uiView = GetRenderer(view).NativeView) + { + page.Layout(new Rectangle(0, 0, view.WidthRequest, view.HeightRequest)); + + + uiView + .AssertColorAtCenter(view.BackgroundColor.ToUIColor()) + .AssertColorAtBottomLeft(EmptyBackground) + .AssertColorAtBottomRight(EmptyBackground) + .AssertColorAtTopLeft(EmptyBackground) + .AssertColorAtTopRight(EmptyBackground); + + } + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/IsEnabledTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/IsEnabledTests.cs new file mode 100644 index 00000000000..e1fb56c9795 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/IsEnabledTests.cs @@ -0,0 +1,53 @@ +using System.Collections; +using NUnit.Framework; +using UIKit; + +namespace Xamarin.Forms.ControlGallery.iOS.Tests +{ + public class IsEnabledTests : PlatformTestFixture + { + static IEnumerable TestCases + { + get + { + // Generate IsEnabled = true cases + foreach (var element in BasicViews) + { + element.IsEnabled = true; + yield return CreateTestCase(element) + .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}"); + } + + // Generate IsEnabled = false cases + foreach (var element in BasicViews) + { + element.IsEnabled = false; + yield return CreateTestCase(element) + .SetName($"{element.GetType().Name}_IsEnabled_{element.IsEnabled}"); + } + } + } + + [Test, Category("IsEnabled"), TestCaseSource(nameof(TestCases))] + [Description("VisualElement enabled should match renderer enabled")] + public void EnabledConsistent(View view) + { + using (var renderer = GetRenderer(view)) + { + var expected = view.IsEnabled; + var nativeView = renderer.NativeView; + + // Check the container + Assert.That(renderer.NativeView.UserInteractionEnabled, Is.EqualTo(expected)); + + // Check the actual control + var control = GetNativeControl(view); + + if (control is UIControl uiControl) + { + Assert.That(uiControl.Enabled, Is.EqualTo(expected)); + } + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/IsVisibleTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/IsVisibleTests.cs new file mode 100644 index 00000000000..8f6597ad6a1 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/IsVisibleTests.cs @@ -0,0 +1,50 @@ +using System.Collections; +using NUnit.Framework; + +namespace Xamarin.Forms.ControlGallery.iOS.Tests +{ + public class IsVisibleTests : PlatformTestFixture + { + static IEnumerable TestCases + { + get + { + // Generate IsVisible = true cases + foreach (var element in BasicViews) + { + element.IsVisible = true; + yield return CreateTestCase(element) + .SetName($"{element.GetType().Name}_IsVisible_{element.IsVisible}"); + } + + // Generate IsEnabled = false cases + foreach (var element in BasicViews) + { + element.IsVisible = false; + yield return CreateTestCase(element) + .SetName($"{element.GetType().Name}_IsVisible_{element.IsVisible}"); + } + } + } + + [Test, Category("IsVisible"), TestCaseSource(nameof(TestCases))] + [Description("VisualElement visibility should match renderer visibility")] + public void VisibleConsistent(View view) + { + var page = new ContentPage() { Content = view }; + + using (var pageRenderer = GetRenderer(page)) + { + using (var uiView = GetRenderer(view).NativeView) + { + page.Layout(new Rectangle(0, 0, 200, 200)); + + var expectedHidden = !view.IsVisible; + var actualHidden = uiView.Layer.Hidden; + + Assert.That(actualHidden, Is.EqualTo(expectedHidden)); + } + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/OpacityTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/OpacityTests.cs new file mode 100644 index 00000000000..842c80aabaa --- /dev/null +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/OpacityTests.cs @@ -0,0 +1,64 @@ +using System.Collections; +using System.Linq; +using NUnit.Framework; + +namespace Xamarin.Forms.ControlGallery.iOS.Tests +{ + [TestFixture] + public class OpacityTests : PlatformTestFixture + { + static readonly double TestOpacity = 0.4; + + static IEnumerable TestCases + { + get + { + foreach (var element in BasicViews.Where(e => !(e is Label))) + { + element.Opacity = TestOpacity; + yield return CreateTestCase(element); + } + } + } + + [Test, Category("Opacity"), TestCaseSource(nameof(TestCases))] + [Description("VisualElement opacity should match renderer opacity")] + public void OpacityConsistent(View view) + { + var page = new ContentPage() { Content = view }; + + using (var pageRenderer = GetRenderer(page)) + { + using (var uiView = GetRenderer(view).NativeView) + { + page.Layout(new Rectangle(0, 0, 200, 200)); + + var expected = view.Opacity; + + // Deliberately casting this to double because Within doesn't seem to grasp nfloat + // If you write this the other way around (casting expected to an nfloat), it fails + Assert.That((double)uiView.Alpha, Is.EqualTo(expected).Within(0.001d)); + } + } + } + + [Test, Category("Opacity"), Category("Label")] + [Description("Label opacity should match renderer opacity")] + public void LabelOpacityConsistent() + { + var label = new Label { Text = "foo", Opacity = TestOpacity }; + + var page = new ContentPage() { Content = label }; + + using (var pageRenderer = GetRenderer(page)) + { + using (var renderer = GetRenderer(label)) + { + page.Layout(new Rectangle(0, 0, 200, 200)); + var expected = label.Opacity; + Assert.That((double)renderer.NativeView.Alpha, Is.EqualTo(expected).Within(0.001d)); + } + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestFixture.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestFixture.cs index 6d0802f06c9..956d3454644 100644 --- a/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestFixture.cs +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestFixture.cs @@ -1,4 +1,6 @@ -using UIKit; +using System.Collections.Generic; +using NUnit.Framework; +using UIKit; using Xamarin.Forms.Platform.iOS; namespace Xamarin.Forms.ControlGallery.iOS.Tests @@ -6,15 +8,79 @@ namespace Xamarin.Forms.ControlGallery.iOS.Tests [Internals.Preserve(AllMembers = true)] public class PlatformTestFixture { + protected static UIColor EmptyBackground = new UIColor(0f,0f,0f,0f); + + // Sequence for generating test cases + protected static IEnumerable BasicViews + { + get + { + yield return new BoxView { }; + yield return new Button { }; + yield return new CheckBox { }; + yield return new DatePicker { }; + yield return new Editor { }; + yield return new Entry { }; + yield return new Frame { }; + yield return new Image { }; + yield return new ImageButton { }; + yield return new Label { }; + yield return new Picker { }; + yield return new ProgressBar { }; + yield return new SearchBar { }; + yield return new Slider { }; + yield return new Stepper { }; + yield return new Switch { }; + yield return new TimePicker { }; + } + } + + protected static TestCaseData CreateTestCase(VisualElement element) + { + // We set the element type as a category on the test so that if you + // filter by category, say, "Button", you'll get any Button test + // generated from here. + + return new TestCaseData(element).SetCategory(element.GetType().Name); + } + protected IVisualElementRenderer GetRenderer(VisualElement element) { return Platform.iOS.Platform.CreateRenderer(element); } + protected UIView GetNativeControl(VisualElement visualElement) + { + var renderer = GetRenderer(visualElement); + var viewRenderer = renderer as IVisualNativeElementRenderer; + return viewRenderer?.Control; + } + protected UILabel GetNativeControl(Label label) { var renderer = GetRenderer(label); - var viewRenderer = renderer.NativeView as ViewRenderer; + var viewRenderer = renderer.NativeView as LabelRenderer; + return viewRenderer.Control; + } + + protected UITextField GetNativeControl(Entry entry) + { + var renderer = GetRenderer(entry); + var viewRenderer = renderer.NativeView as EntryRenderer; + return viewRenderer.Control; + } + + protected UITextView GetNativeControl(Editor editor) + { + var renderer = GetRenderer(editor); + var viewRenderer = renderer.NativeView as EditorRenderer; + return viewRenderer.Control; + } + + protected UIButton GetNativeControl(Button button) + { + var renderer = GetRenderer(button); + var viewRenderer = renderer.NativeView as ButtonRenderer; return viewRenderer.Control; } } diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestSettings.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestSettings.cs index ce718130f89..6989d2aaa6e 100644 --- a/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestSettings.cs +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/PlatformTestSettings.cs @@ -16,8 +16,8 @@ public PlatformTestSettings() { TestRunSettings = new Dictionary { - // Creating/modifying any renderers on iOS off the UI thread causes problems - // so at least for iOS, we want to force the tests to run on main + // Creating/modifying any renderers off the UI thread causes problems + // so we want to force the tests to run on main { FrameworkPackageSettings.RunOnMainThread, true } }; } diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/RendererTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/RendererTests.cs deleted file mode 100644 index 299e36dd530..00000000000 --- a/Xamarin.Forms.ControlGallery.iOS/Tests/RendererTests.cs +++ /dev/null @@ -1,19 +0,0 @@ -using NUnit.Framework; - -namespace Xamarin.Forms.ControlGallery.iOS.Tests -{ - [TestFixture] - [Internals.Preserve(AllMembers = true)] - public class RendererTests : PlatformTestFixture - { - [Test(Description = "Basic sanity check that Label text matches renderer text")] - public void LabelTextMatchesRendererText() - { - var label = new Label { Text = "foo" }; - using (var uiLabel = GetNativeControl(label)) - { - Assert.That(label.Text == uiLabel.Text); - } - } - } -} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/RotationTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/RotationTests.cs new file mode 100644 index 00000000000..35ef5be0f79 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/RotationTests.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections; +using NUnit.Framework; +using UIKit; +using static Xamarin.Forms.Core.UITests.NumericExtensions; +using static Xamarin.Forms.Core.UITests.ParsingUtils; + +namespace Xamarin.Forms.ControlGallery.iOS.Tests +{ + [TestFixture] + public class RotationTests : PlatformTestFixture + { + static IEnumerable RotationXCases + { + get + { + foreach (var element in BasicViews) + { + element.RotationX = 33.0; + yield return CreateTestCase(element); + } + } + } + + static IEnumerable RotationYCases + { + get + { + foreach (var element in BasicViews) + { + element.RotationY = 87.0; + yield return CreateTestCase(element); + } + } + } + + static IEnumerable RotationCases + { + get + { + foreach (var element in BasicViews) + { + element.Rotation = 23.0; + yield return CreateTestCase(element); + } + } + } + + void AssertRotationConsistent(View view, Func getRotation, + Func getNativeRotation) + { + var page = new ContentPage() { Content = view }; + + using (var pageRenderer = GetRenderer(page)) + { + using (var uiView = GetRenderer(view).NativeView) + { + page.Layout(new Rectangle(0, 0, 200, 200)); + + var expected = getRotation(view); + var actual = getNativeRotation(uiView); + + Assert.That(actual, Is.EqualTo(expected)); + } + } + } + + [Test, Category("RotationX"), TestCaseSource(nameof(RotationXCases))] + [Description("VisualElement X rotation should match renderer X rotation")] + public void RotationXConsistent(View view) + { + AssertRotationConsistent(view, + e => CalculateRotationMatrixForDegrees((float)e.RotationX, Core.UITests.Axis.X), + v => ParseCATransform3D(v.Layer.Transform.ToString())); + } + + [Test, Category("RotationY"), TestCaseSource(nameof(RotationYCases))] + [Description("VisualElement Y rotation should match renderer Y rotation")] + public void RotationYConsistent(View view) + { + AssertRotationConsistent(view, + e => CalculateRotationMatrixForDegrees((float)e.RotationY, Core.UITests.Axis.Y), + v => ParseCATransform3D(v.Layer.Transform.ToString())); + } + + [Test, Category("Rotation"), TestCaseSource(nameof(RotationCases))] + [Description("VisualElement rotation should match renderer rotation")] + public void RotationConsistent(View view) + { + AssertRotationConsistent(view, + e => CalculateRotationMatrixForDegrees((float)e.Rotation, Core.UITests.Axis.Z), + v => ParseCATransform3D(v.Layer.Transform.ToString())); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/ScaleTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/ScaleTests.cs new file mode 100644 index 00000000000..e307ab01e97 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/ScaleTests.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections; +using NUnit.Framework; +using UIKit; +using static Xamarin.Forms.Core.UITests.NumericExtensions; +using static Xamarin.Forms.Core.UITests.ParsingUtils; + +namespace Xamarin.Forms.ControlGallery.iOS.Tests +{ + [TestFixture] + public class ScaleTests : PlatformTestFixture + { + static IEnumerable ScaleCases + { + get + { + foreach (var element in BasicViews) + { + element.Scale = 2.0; + yield return CreateTestCase(element); + } + } + } + + void AssertScaleConsistent(View view, Func getScale, + Func getNativeScale) + { + var page = new ContentPage() { Content = view }; + + using (var pageRenderer = GetRenderer(page)) + { + using (var uiView = GetRenderer(view).NativeView) + { + page.Layout(new Rectangle(0, 0, 200, 200)); + + var expected = getScale(view); + var actual = getNativeScale(uiView); + + Assert.That(actual, Is.EqualTo(expected)); + } + } + } + + [Test, Category("Scale"), TestCaseSource(nameof(ScaleCases))] + [Description("View scale should match renderer scale")] + public void ScaleConsistent(View view) + { + AssertScaleConsistent(view, + e => BuildScaleMatrix((float)e.Scale), + v => ParseCATransform3D(v.Layer.Transform.ToString())); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/TestingPlatformService.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/TestingPlatformService.cs new file mode 100644 index 00000000000..b7ee6b070b2 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/TestingPlatformService.cs @@ -0,0 +1,15 @@ +using Xamarin.Forms; +using Xamarin.Forms.ControlGallery.iOS.Tests; +using Xamarin.Forms.Controls.Tests; + +[assembly: Dependency(typeof(TestingPlatformService))] +namespace Xamarin.Forms.ControlGallery.iOS.Tests +{ + class TestingPlatformService : ITestingPlatformService + { + public void CreateRenderer(VisualElement visualElement) + { + Platform.iOS.Platform.CreateRenderer(visualElement); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.iOS/Tests/TextTests.cs b/Xamarin.Forms.ControlGallery.iOS/Tests/TextTests.cs new file mode 100644 index 00000000000..2b006692539 --- /dev/null +++ b/Xamarin.Forms.ControlGallery.iOS/Tests/TextTests.cs @@ -0,0 +1,52 @@ +using NUnit.Framework; + +namespace Xamarin.Forms.ControlGallery.iOS.Tests +{ + [TestFixture] + public class TextTests : PlatformTestFixture + { + [Test, Category("Text"), Category("Label")] + [Description("Label text should match renderer text")] + public void LabelTextMatchesRendererText() + { + var label = new Label { Text = "foo" }; + using (var uiLabel = GetNativeControl(label)) + { + Assert.That(label.Text, Is.EqualTo(uiLabel.Text)); + } + } + + [Test, Category("Text"), Category("Button")] + [Description("Button text should match renderer text")] + public void ButtonTextMatchesRendererText() + { + var button = new Button { Text = "foo" }; + using (var nativeControl = GetNativeControl(button)) + { + Assert.That(nativeControl.TitleLabel.Text, Is.EqualTo(button.Text)); + } + } + + [Test, Category("Text"), Category("Entry")] + [Description("Entry text should match renderer text")] + public void EntryTextMatchesRendererText() + { + var entry = new Entry { Text = "foo" }; + using (var nativeControl = GetNativeControl(entry)) + { + Assert.That(nativeControl.Text, Is.EqualTo(entry.Text)); + } + } + + [Test, Category("Text"), Category("Editor")] + [Description("Editor text should match renderer text")] + public void EditorTextMatchesRendererText() + { + var editor = new Editor { Text = "foo" }; + using (var nativeControl = GetNativeControl(editor)) + { + Assert.That(nativeControl.Text, Is.EqualTo(editor.Text)); + } + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj index be1523157a3..4ebf2389154 100644 --- a/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj +++ b/Xamarin.Forms.ControlGallery.iOS/Xamarin.Forms.ControlGallery.iOS.csproj @@ -105,6 +105,12 @@ + + Tests\NumericExtensions.cs + + + Tests\ParsingUtils.cs + @@ -117,9 +123,18 @@ + + + + + + - + + + + diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla29128.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla29128.cs index 954af170622..ddf545b62f8 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla29128.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla29128.cs @@ -2,7 +2,9 @@ using Xamarin.Forms.CustomAttributes; using Xamarin.Forms.Internals; + #if UITEST +using Xamarin.Forms.Core.UITests; using Xamarin.UITest; using NUnit.Framework; #endif @@ -25,6 +27,7 @@ protected override void Init () #if UITEST [Test] + [Category(UITestCategories.ManualReview)] public void Bugzilla29128Test () { RunningApp.WaitForElement (q => q.Marked ("SliderId")); diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla35738.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla35738.cs deleted file mode 100644 index 5b8132fd322..00000000000 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla35738.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Diagnostics; -using Xamarin.Forms.CustomAttributes; -using Xamarin.Forms.Internals; - -#if UITEST -using Xamarin.UITest; -using NUnit.Framework; -#endif - -namespace Xamarin.Forms.Controls.Issues -{ - [Preserve (AllMembers = true)] - public class CustomButton : Button - { - // In the Android project, there's a custom renderer set up for this type - } - - [Preserve (AllMembers = true)] - [Issue (IssueTracker.Bugzilla, 35738, "ButtonRenderer UpdateTextColor function crash", PlatformAffected.Android)] - public class Bugzilla35738 : TestContentPage - { - protected override void Init () - { - var label = new Label () { Text = "If you can see the button, this test has passed" }; - var customButton = new CustomButton () { Text = "This is a custom button", TextColor = Color.Fuchsia }; - - Content = new StackLayout () { - Children = { label, customButton } - }; - } - -#if UITEST - [Test] - [UiTest (typeof(TestContentPage))] - public void CallingOnElementChangedOnCustomButtonShouldNotCrash () - { - RunningApp.WaitForElement (q => q.Marked ("This is a custom button")); - } -#endif - } -} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs index 1e4c5e5bad0..3940eb73590 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla51173.cs @@ -128,7 +128,7 @@ protected override void Init() var instructions = new Label { Text = - "Pressing the 'Valid Image' button should display an image of a coffee cup. Every other button should cause the messager 'Error logged' to appear at the top of the page." + "Pressing the 'Valid Image' button should display an image of a coffee cup. Every other button should cause the message 'Error logged' to appear at the top of the page." }; _image = new Image { BackgroundColor = Color.Bisque, HeightRequest = 20 }; diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2674.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2674.cs new file mode 100644 index 00000000000..fa42b75b855 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2674.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.Forms.Core.UITests; +using Xamarin.UITest; +using NUnit.Framework; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ +#if UITEST + [Category(UITestCategories.Picker)] +#endif + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 2674, "Exception occurs when giving null values in picker itemsource collection", PlatformAffected.All)] + public class Issue2674 : TestContentPage + { + protected override void Init() + { + var _picker = new Picker() + { + ItemsSource = new List { "cat", null, "rabbit" }, + AutomationId = "picker", + }; + + Content = new StackLayout() + { + Children = + { + _picker + } + }; + } + +#if UITEST + [Test] + public void Issue2674Test() + { + RunningApp.Screenshot("I am at Issue2674"); + RunningApp.WaitForElement("picker"); + } +#endif + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml new file mode 100644 index 00000000000..8003110e77d --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml.cs new file mode 100644 index 00000000000..348b60b55b5 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue4459.xaml.cs @@ -0,0 +1,29 @@ +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 4459, "[UWP] BoxView CornerRadius doesn't work", PlatformAffected.UWP)] +#if APP + [XamlCompilation(XamlCompilationOptions.Compile)] +#endif + public partial class Issue4459 : ContentPage + { + public Issue4459() + { +#if APP + InitializeComponent(); +#endif + } + + void InputView_OnTextChanged(object sender, TextChangedEventArgs e) + { +#if APP + BoxView.CornerRadius = new CornerRadius(double.Parse(TopLeft.Text), double.Parse(TopRight.Text), + double.Parse(BottomLeft.Text), double.Parse(BottomRight.Text)); +#endif + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5150.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5150.cs new file mode 100644 index 00000000000..2d9ec61729e --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue5150.cs @@ -0,0 +1,148 @@ +using System.ComponentModel; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using CategoryAttribute = NUnit.Framework.CategoryAttribute; +using Xamarin.Forms.Core.UITests; +using Xamarin.UITest; +using NUnit.Framework; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ + + public static class ContentDescriptionEffectProperties + { + public static readonly BindableProperty ContentDescriptionProperty = BindableProperty.CreateAttached( + "ContentDescription", + typeof(string), + typeof(ContentDescriptionEffectProperties), + null); + + public static readonly BindableProperty NameAndHelpTextProperty = BindableProperty.CreateAttached( + "NameAndHelpText", + typeof(string), + typeof(ContentDescriptionEffectProperties), + null); + + public static string GetContentDescription(BindableObject view) + { + return (string)view.GetValue(ContentDescriptionProperty); + } + + public static string GetNameAndHelpText(BindableObject view) + { + return (string)view.GetValue(NameAndHelpTextProperty); + } + } + + public class ContentDescriptionEffect : RoutingEffect + { + public const string EffectName = "ContentDescriptionEffect"; + + public ContentDescriptionEffect() : base($"{Effects.ResolutionGroupName}.{EffectName}") + { + } + } + + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 5150, "AutomationProperties.Name, AutomationProperties.HelpText on Button not read by Android TalkBack", PlatformAffected.Android)] + public class Issue5150 : TestContentPage // or TestMasterDetailPage, etc ... + { + void AddView(StackLayout layout, View view, string labelPrefix, string automationId, string buttonName = null, string buttonHelp = null) + { + var automationIdLabel = new Label(); + automationIdLabel.Text = $"AutomationId = {automationId}"; + automationIdLabel.AutomationId = $"{labelPrefix}-automationIdLabel"; + + var contentDescriptionLabel = new Label(); + contentDescriptionLabel.AutomationId = $"{labelPrefix}-contentDescriptionLabel"; + + var nameAndHelpTextLabel = new Label(); + nameAndHelpTextLabel.AutomationId = $"{labelPrefix}-nameAndHelpTextLabel"; + + view.AutomationId = automationId; + view.Effects.Add(new ContentDescriptionEffect()); + view.PropertyChanged += (object sender, PropertyChangedEventArgs e) => { + if (e.PropertyName == ContentDescriptionEffectProperties.ContentDescriptionProperty.PropertyName) + { + contentDescriptionLabel.Text = $"ContentDescription = {ContentDescriptionEffectProperties.GetContentDescription(view)}"; + } + + if (e.PropertyName == ContentDescriptionEffectProperties.NameAndHelpTextProperty.PropertyName) + { + nameAndHelpTextLabel.Text = $"Name + HelpText = {ContentDescriptionEffectProperties.GetNameAndHelpText(view)}"; + } + }; + layout.Children.Add(view); + layout.Children.Add(automationIdLabel); + layout.Children.Add(contentDescriptionLabel); + layout.Children.Add(nameAndHelpTextLabel); + + AutomationProperties.SetIsInAccessibleTree(view, true); + AutomationProperties.SetName(view, buttonName); + AutomationProperties.SetHelpText(view, buttonHelp); + } + + void AddButton(StackLayout layout, string labelPrefix, string automationId, string buttonText, string buttonName = null, string buttonHelp = null) + { + var button = new Button(); + button.Text = buttonText; + AddView(layout, button, labelPrefix, automationId, buttonName, buttonHelp); + } + + protected override void Init() + { + var scrollView = new ScrollView(); + var layout = new StackLayout(); + scrollView.Content = layout; + + layout.Children.Add(new Label + { + Text = "On the Android platform, the 'Name + Help Text' " + + "labels below each button should match the text read by " + + "TalkBack without interferring with the AutomationId and " + + "ContentDescription." + }); + + AddButton(layout, "button1prop", "button1", "Button 1", "Name 1"); + AddButton(layout, "button2prop", "button2", "Button 2", buttonHelp: "Help 2."); + AddButton(layout, "button3prop", "button3", "Button 3", "Name 3", "Help 3."); + AddButton(layout, "button4prop", "button4", null , buttonHelp: "Help 4."); + + AddView(layout, new Switch(), "switch1prop", "switch1", "Switch 1 Name", "Switch Help 1."); + + var image = new Image(); + image.Source = ImageSource.FromFile("coffee.png"); + AddView(layout, image, "image1prop", "image1", "Coffee", "Get some coffee!"); + + Content = scrollView; + } + +#if UITEST + void Verify(string labelPrefix, string automationId, string expectedNameAndHelpText) + { + RunningApp.ScrollTo(automationId); + RunningApp.WaitForElement(q => q.Marked(automationId)); + RunningApp.ScrollTo($"{labelPrefix}-nameAndHelpTextLabel"); + RunningApp.WaitForElement(q => q.Text($"Name + HelpText = {expectedNameAndHelpText}")); + } + + [Test] + [Category(UITestCategories.Button)] +#if !__ANDROID__ + [Ignore("This test verifies ContentDescription is set on the Android platform.")] +#endif + public void Issue5150Test() + { + Verify("button1prop", "button1", "Name 1"); + Verify("button2prop", "button2", "Button 2. Help 2."); + Verify("button3prop", "button3", "Name 3. Help 3."); + Verify("button4prop", "button4", "Help 4."); + Verify("switch1prop", "switch1", "Switch 1 Name. Switch Help 1."); + Verify("image1prop", "image1", "Coffee. Get some coffee!"); + } +#endif + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932.xaml b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932.xaml new file mode 100644 index 00000000000..e4ab6d3367f --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue6932.xaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + +