diff --git a/Microsoft.Maui.sln b/Microsoft.Maui.sln index 80664e9cd239..57e80e0dbecb 100644 --- a/Microsoft.Maui.sln +++ b/Microsoft.Maui.sln @@ -255,6 +255,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Controls.Core.Design.UnitTe EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.DeviceTests.Shared", "src\Core\tests\DeviceTests.Shared\Core.DeviceTests.Shared.csproj", "{66CC98E3-6A1A-4C44-A23C-B575E82106EC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AccessibilityCheck.Droid", "src\TestUtils\src\AccessibilityCheck.Droid\AccessibilityCheck.Droid.csproj", "{0F716D53-6EEF-4F87-A528-232BFA3AD044}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -637,6 +639,10 @@ Global {66CC98E3-6A1A-4C44-A23C-B575E82106EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {66CC98E3-6A1A-4C44-A23C-B575E82106EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {66CC98E3-6A1A-4C44-A23C-B575E82106EC}.Release|Any CPU.Build.0 = Release|Any CPU + {0F716D53-6EEF-4F87-A528-232BFA3AD044}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F716D53-6EEF-4F87-A528-232BFA3AD044}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F716D53-6EEF-4F87-A528-232BFA3AD044}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F716D53-6EEF-4F87-A528-232BFA3AD044}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -755,6 +761,7 @@ Global {F351A992-18E4-473C-8ADD-2BA0BAA7B5A2} = {1BA0121E-0B83-4C8F-81BE-C293E7E35DCE} {F68932B0-81A2-4CC3-A4F7-28091EE91B23} = {25D0D27A-C5FE-443D-8B65-D6C987F4A80E} {66CC98E3-6A1A-4C44-A23C-B575E82106EC} = {C564DDD6-DE79-45CD-88EA-3F690481572A} + {0F716D53-6EEF-4F87-A528-232BFA3AD044} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50} diff --git a/src/Controls/tests/DeviceTests/TestCategory.cs b/src/Controls/tests/DeviceTests/TestCategory.cs deleted file mode 100644 index 5ec97753a6ed..000000000000 --- a/src/Controls/tests/DeviceTests/TestCategory.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Microsoft.Maui.DeviceTests -{ - public static class TestCategory - { - public const string Accessibility = "Accessibility"; - public const string Application = "Application"; - public const string Behavior = "Behavior"; - public const string Button = "Button"; - public const string CheckBox = "CheckBox"; - public const string Compatibility = "Compatibility"; - public const string ContentView = "ContentView"; - public const string Dispatcher = "Dispatcher"; - public const string Editor = "Editor"; - public const string Element = "Element"; - public const string Entry = "Entry"; - public const string Frame = "Frame"; - public const string FlyoutPage = "FlyoutPage"; - public const string Gesture = "Gesture"; - public const string Image = "Image"; - public const string Label = "Label"; - public const string Layout = "Layout"; - public const string ListView = "ListView"; - public const string MenuFlyout = nameof(MenuFlyout); - public const string Modal = "Modal"; - public const string NavigationPage = "NavigationPage"; - public const string Page = "Page"; - public const string Picker = "Picker"; - public const string ScrollView = "ScrollView"; - public const string SearchBar = "SearchBar"; - public const string Shell = "Shell"; - public const string TabbedPage = "TabbedPage"; - public const string Toolbar = "Toolbar"; - public const string TemplatedView = "TemplatedView"; - public const string VisualElement = "VisualElement"; - public const string VisualElementTree = "VisualElementTree"; - public const string Window = "Window"; - } -} diff --git a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Android.cs b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Android.cs index 3896abeb3b0a..d7482959604e 100644 --- a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Android.cs +++ b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Android.cs @@ -2,6 +2,7 @@ using System.Numerics; using System.Threading.Tasks; using Android.Views; +using Android.Webkit; using Android.Widget; using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; @@ -168,6 +169,23 @@ public async Task NeedsContainerWhenInputTransparent() Assert.True(vh.NeedsContainer); } + //[Fact(DisplayName = "Control meets basic accessibility requirements")] + //[Category(TestCategory.Accessibility)] + protected async Task AssertPlatformViewIsAccessible(TStub view) + { + var handler = await CreateHandlerAsync(view); + + var platformView = handler.PlatformView as View; + + await InvokeOnMainThreadAsync(async () => + { + await platformView.AttachAndRun(() => + { + TestUtils.DeviceTests.Accessibility.AssertAccessible(platformView); + }); + }); + } + protected string GetAutomationId(IViewHandler viewHandler) => $"{GetSemanticPlatformElement(viewHandler).ContentDescription}"; diff --git a/src/Core/tests/DeviceTests/TestCategory.cs b/src/Core/tests/DeviceTests.Shared/TestCategory.cs similarity index 65% rename from src/Core/tests/DeviceTests/TestCategory.cs rename to src/Core/tests/DeviceTests.Shared/TestCategory.cs index 515f0dd057e5..60b8106ce9c8 100644 --- a/src/Core/tests/DeviceTests/TestCategory.cs +++ b/src/Core/tests/DeviceTests.Shared/TestCategory.cs @@ -1,23 +1,36 @@ -namespace Microsoft.Maui.DeviceTests +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Maui.DeviceTests { public static class TestCategory { public const string MauiContext = "MauiContext"; + public const string Accessibility = "Accessibility"; public const string Application = "Application"; public const string ActivityIndicator = "ActivityIndicator"; + public const string Behavior = "Behavior"; public const string Border = "Border"; public const string BoxView = "BoxView"; public const string Button = "Button"; public const string CheckBox = "CheckBox"; + public const string Compatibility = "Compatibility"; public const string ContentView = "ContentView"; public const string DatePicker = "DatePicker"; public const string Dispatcher = "Dispatcher"; public const string Editor = "Editor"; + public const string Element = "Element"; public const string Entry = "Entry"; public const string FlowDirection = "FlowDirection"; + public const string FlyoutPage = "FlyoutPage"; public const string FlyoutView = "FlyoutView"; public const string Fonts = "Fonts"; + public const string Frame = "Frame"; + public const string Gesture = "Gesture"; public const string GraphicsView = "GraphicsView"; public const string Image = "Image"; public const string ImageButton = "ImageButton"; @@ -25,6 +38,10 @@ public static class TestCategory public const string IndicatorView = "IndicatorView"; public const string Label = "Label"; public const string Layout = "Layout"; + public const string ListView = "ListView"; + public const string MenuFlyout = "MenuFlyout"; + public const string Modal = "Modal"; + public const string NavigationPage = "NavigationPage"; public const string NavigationView = "NavigationView"; public const string Page = "Page"; public const string Picker = "Picker"; @@ -33,12 +50,18 @@ public static class TestCategory public const string ScrollView = "ScrollView"; public const string SearchBar = "SearchBar"; public const string ShapeView = "ShapeView"; + public const string Shell = "Shell"; public const string Slider = "Slider"; public const string Stepper = "Stepper"; public const string Switch = "Switch"; + public const string TabbedPage = "TabbedPage"; + public const string TemplatedView = "TemplatedView"; public const string TextFormatting = "Formatting"; public const string TimePicker = "TimePicker"; + public const string Toolbar = "Toolbar"; public const string View = "View"; + public const string VisualElement = "VisualElement"; + public const string VisualElementTree = "VisualElementTree"; public const string WebView = "WebView"; public const string Window = "Window"; public const string WindowOverlay = "WindowOverlay"; diff --git a/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.Android.cs index 2bdd59c97ba8..26200bd8b19f 100644 --- a/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/ActivityIndicator/ActivityIndicatorHandlerTests.Android.cs @@ -42,5 +42,13 @@ public override async Task SetVisibility(Visibility visibility) var id = await GetValueAsync(view, handler => GetVisibility(handler)); Assert.Equal(view.Visibility, id); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new ActivityIndicatorStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Border/BorderHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Border/BorderHandlerTests.Android.cs index 93b530892535..bc93bf4c65d7 100644 --- a/src/Core/tests/DeviceTests/Handlers/Border/BorderHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Border/BorderHandlerTests.Android.cs @@ -63,5 +63,13 @@ Task ValidateHasColor(IBorderView border, Color color, Action action = null) nativeBorder.AssertContainsColor(color); }); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new BorderStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/BoxView/BoxViewHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/BoxView/BoxViewHandlerTests.Android.cs index dc9c0fd581f4..6b83b05ab863 100644 --- a/src/Core/tests/DeviceTests/Handlers/BoxView/BoxViewHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/BoxView/BoxViewHandlerTests.Android.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Microsoft.Maui.DeviceTests.Stubs; using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; @@ -19,5 +20,13 @@ Task ValidateHasColor(IShapeView boxView, Color color, Action action = null) nativeBoxView.AssertContainsColor(color); }); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new BoxViewStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.Android.cs index 206e1d1a83a1..af022255bc74 100644 --- a/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.Android.cs @@ -167,5 +167,18 @@ Task ValidateHasColor(IButton button, Color color, Action action = null) platformButton.AssertContainsColor(color); }); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new ButtonStub(); + + // A button won't be considered accessible if it doesn't have text + // for a screen reader + view.Text = "Button Text"; + + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.Android.cs index 6a4e75006a03..399f3b465995 100644 --- a/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/CheckBox/CheckBoxHandlerTests.Android.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using AndroidX.AppCompat.Widget; +using Microsoft.Maui.DeviceTests.Stubs; using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; @@ -26,5 +27,13 @@ Task ValidateHasColor(ICheckBox checkBoxStub, Color color, Action action = null) nativeSwitch.AssertContainsColor(color); }); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new CheckBoxStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/DatePicker/DatePickerHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/DatePicker/DatePickerHandlerTests.Android.cs index 2c1f1a830126..e5e0bb8b2ac7 100644 --- a/src/Core/tests/DeviceTests/Handlers/DatePicker/DatePickerHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/DatePicker/DatePickerHandlerTests.Android.cs @@ -126,5 +126,13 @@ double GetNativeCharacterSpacing(DatePickerHandler datePickerHandler) var mauiDatePicker = GetNativeDatePicker(datePickerHandler); return mauiDatePicker.LetterSpacing; } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new DatePickerStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.Android.cs index 4e59d92534ca..1523a72384e2 100644 --- a/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.Android.cs @@ -222,5 +222,16 @@ int GetNativeSelectionLength(EditorHandler editorHandler) return -1; } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new EditorStub(); + + view.Semantics = new Semantics() { Hint = "Editor Hint" }; + + await AssertPlatformViewIsAccessible(view); + } } } diff --git a/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.Android.cs index 716f4ebb098d..8ae7e83c38c5 100644 --- a/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.Android.cs @@ -291,5 +291,13 @@ int GetNativeSelectionLength(EntryHandler entryHandler) return -1; } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new EntryStub(); + await AssertPlatformViewIsAccessible(view); + } } } diff --git a/src/Core/tests/DeviceTests/Handlers/Image/ImageHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Image/ImageHandlerTests.Android.cs index c2dc80a2cb69..cbacdf824428 100644 --- a/src/Core/tests/DeviceTests/Handlers/Image/ImageHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Image/ImageHandlerTests.Android.cs @@ -89,5 +89,13 @@ Aspect GetNativeAspect(IImageHandler imageHandler) throw new ArgumentOutOfRangeException("Aspect"); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new TStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/ImageButton/ImageButtonHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/ImageButton/ImageButtonHandlerTests.Android.cs index a12eaf6a8a6b..687752c7d5ac 100644 --- a/src/Core/tests/DeviceTests/Handlers/ImageButton/ImageButtonHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/ImageButton/ImageButtonHandlerTests.Android.cs @@ -56,5 +56,16 @@ Task ValidateHasColor(IImageButton imageButton, Color color, Action action = nul bool ImageSourceLoaded(ImageButtonHandler imageButtonHandler) => imageButtonHandler.PlatformView.Drawable != null; + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new ImageButtonStub(); + + view.Semantics = new Semantics() { Description = "Button Description" }; + + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.Android.cs index 7dd140fcfccc..cdea333ebc02 100644 --- a/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Label/LabelHandlerTests.Android.cs @@ -134,5 +134,13 @@ Task ValidateHasColor(ILabel label, Color color, Action action = null) platformLabel.AssertContainsColor(color); }); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new LabelStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs index 6b472900d592..363f299b5065 100644 --- a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.Android.cs @@ -122,5 +122,13 @@ Color GetNativeTextColor(PickerHandler pickerHandler) GravityFlags GetNativeVerticalTextAlignment(PickerHandler pickerHandler) => GetNativePicker(pickerHandler).Gravity; + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new PickerStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/ProgressBar/ProgressBarHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/ProgressBar/ProgressBarHandlerTests.Android.cs index 58d993814159..94e18f9650a7 100644 --- a/src/Core/tests/DeviceTests/Handlers/ProgressBar/ProgressBarHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/ProgressBar/ProgressBarHandlerTests.Android.cs @@ -3,6 +3,7 @@ using Microsoft.Maui.Graphics; using Microsoft.Maui.Handlers; using AProgressBar = Android.Widget.ProgressBar; +using Microsoft.Maui.DeviceTests.Stubs; namespace Microsoft.Maui.DeviceTests { @@ -26,5 +27,13 @@ Task ValidateHasColor(IProgress progressBar, Color color, Action action = null) platformProgressBar.AssertContainsColor(color); }); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new ProgressBarStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/RadioButton/RadioButtonHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/RadioButton/RadioButtonHandlerTests.Android.cs index d80c9b53782a..f45183236acd 100644 --- a/src/Core/tests/DeviceTests/Handlers/RadioButton/RadioButtonHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/RadioButton/RadioButtonHandlerTests.Android.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using AndroidX.AppCompat.Widget; +using Microsoft.Maui.DeviceTests.Stubs; namespace Microsoft.Maui.DeviceTests { @@ -14,5 +15,13 @@ AppCompatRadioButton GetNativeRadioButton(RadioButtonHandler radioButtonHandler) bool GetNativeIsChecked(RadioButtonHandler radioButtonHandler) => GetNativeRadioButton(radioButtonHandler).Checked; + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new RadioButtonStub(); + await AssertPlatformViewIsAccessible(view); + } } } diff --git a/src/Core/tests/DeviceTests/Handlers/RefreshView/RefreshViewHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/RefreshView/RefreshViewHandlerTests.Android.cs index 3daead48cf86..7939e88b522a 100644 --- a/src/Core/tests/DeviceTests/Handlers/RefreshView/RefreshViewHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/RefreshView/RefreshViewHandlerTests.Android.cs @@ -1,4 +1,6 @@ -using Microsoft.Maui.Handlers; +using System.Threading.Tasks; +using Microsoft.Maui.DeviceTests.Stubs; +using Microsoft.Maui.Handlers; namespace Microsoft.Maui.DeviceTests { @@ -9,5 +11,13 @@ MauiSwipeRefreshLayout GetNativeRefreshView(RefreshViewHandler RefreshViewHandle bool GetPlatformIsRefreshing(RefreshViewHandler RefreshViewHandler) => GetNativeRefreshView(RefreshViewHandler).Refreshing; + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new RefreshViewStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/ScrollView/ScrollViewHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/ScrollView/ScrollViewHandlerTests.Android.cs index f8d534aec5c9..149d0934a9fd 100644 --- a/src/Core/tests/DeviceTests/Handlers/ScrollView/ScrollViewHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/ScrollView/ScrollViewHandlerTests.Android.cs @@ -99,5 +99,13 @@ public async Task VerticalVisibilityInitializesCorrectly(ScrollBarVisibility vis Assert.Equal(expected, result); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new ScrollViewStub(); + await AssertPlatformViewIsAccessible(view); + } } } diff --git a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs index 164db4dd33a4..be1747b578e6 100644 --- a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs @@ -235,5 +235,13 @@ bool GetNativeIsReadOnly(SearchBarHandler searchBarHandler) return !editText.Focusable && !editText.FocusableInTouchMode; } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new SearchBarStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/ShapeView/ShapeViewHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/ShapeView/ShapeViewHandlerTests.Android.cs index 80525fb3279c..bf99db6a0cab 100644 --- a/src/Core/tests/DeviceTests/Handlers/ShapeView/ShapeViewHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/ShapeView/ShapeViewHandlerTests.Android.cs @@ -49,5 +49,13 @@ Task ValidateHasColor(IView shape, Color color, Action action = null) nativeShape.AssertContainsColor(color); }); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new ShapeViewStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Slider/SliderHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Slider/SliderHandlerTests.Android.cs index 6eeb80821dbf..9a51fb936cb6 100644 --- a/src/Core/tests/DeviceTests/Handlers/Slider/SliderHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Slider/SliderHandlerTests.Android.cs @@ -102,5 +102,13 @@ public async Task ValueInitializesCorrectly() Assert.Equal(xplatValue, values.ViewValue); Assert.Equal(expectedValue, values.PlatformViewValue); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new SliderStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Stepper/StepperHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Stepper/StepperHandlerTests.Android.cs index 6569cbaf3776..0799d471bcc2 100644 --- a/src/Core/tests/DeviceTests/Handlers/Stepper/StepperHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Stepper/StepperHandlerTests.Android.cs @@ -75,5 +75,13 @@ Task ValidateHasColor(IStepper stepper, Color color, Action action = null) platformStepper.AssertContainsColor(color); }); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new StepperStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.Android.cs index f68511465298..31598b140a1e 100644 --- a/src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/Switch/SwitchHandlerTests.Android.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Android.Graphics; +using Microsoft.Maui.DeviceTests.Stubs; using Microsoft.Maui.Handlers; using ASwitch = AndroidX.AppCompat.Widget.SwitchCompat; using Color = Microsoft.Maui.Graphics.Color; @@ -33,5 +34,13 @@ Task ValidateHasColor(ISwitch switchStub, Color color, Action action = null) return nativeSwitch.AssertContainsColor(color); }); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new SwitchStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/TimePicker/TimePickerHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/TimePicker/TimePickerHandlerTests.Android.cs index f08bf9b2a9bf..a62db4c1f5e2 100644 --- a/src/Core/tests/DeviceTests/Handlers/TimePicker/TimePickerHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/TimePicker/TimePickerHandlerTests.Android.cs @@ -72,5 +72,13 @@ Color GetNativeTextColor(TimePickerHandler timePickerHandler) AColor currentTextColor = new AColor(currentTextColorInt); return currentTextColor.ToColor(); } + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new TimePickerStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs index 250dc2caf5bb..60299cd4b809 100644 --- a/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs +++ b/src/Core/tests/DeviceTests/Handlers/WebView/WebViewHandlerTests.Android.cs @@ -1,4 +1,6 @@ -using Microsoft.Maui.Handlers; +using System.Threading.Tasks; +using Microsoft.Maui.DeviceTests.Stubs; +using Microsoft.Maui.Handlers; using AWebView = Android.Webkit.WebView; namespace Microsoft.Maui.DeviceTests @@ -10,5 +12,13 @@ AWebView GetNativeWebView(WebViewHandler webViewHandler) => string GetNativeSource(WebViewHandler webViewHandler) => GetNativeWebView(webViewHandler).Url; + + [Fact(DisplayName = "Control meets basic accessibility requirements")] + [Category(TestCategory.Accessibility)] + public async Task PlatformViewIsAccessible() + { + var view = new WebViewStub(); + await AssertPlatformViewIsAccessible(view); + } } } \ No newline at end of file diff --git a/src/TestUtils/src/AccessibilityCheck.Droid/AccessibilityCheck.Droid.csproj b/src/TestUtils/src/AccessibilityCheck.Droid/AccessibilityCheck.Droid.csproj new file mode 100644 index 000000000000..843c48cd9928 --- /dev/null +++ b/src/TestUtils/src/AccessibilityCheck.Droid/AccessibilityCheck.Droid.csproj @@ -0,0 +1,11 @@ + + + net6.0-android + + enable + + + + + + \ No newline at end of file diff --git a/src/TestUtils/src/AccessibilityCheck.Droid/Additions/AboutAdditions.txt b/src/TestUtils/src/AccessibilityCheck.Droid/Additions/AboutAdditions.txt new file mode 100644 index 000000000000..2775bd360d20 --- /dev/null +++ b/src/TestUtils/src/AccessibilityCheck.Droid/Additions/AboutAdditions.txt @@ -0,0 +1,48 @@ +Additions allow you to add arbitrary C# to the generated classes +before they are compiled. This can be helpful for providing convenience +methods or adding pure C# classes. + +== Adding Methods to Generated Classes == + +Let's say the library being bound has a Rectangle class with a constructor +that takes an x and y position, and a width and length size. It will look like +this: + +public partial class Rectangle +{ + public Rectangle (int x, int y, int width, int height) + { + // JNI bindings + } +} + +Imagine we want to add a constructor to this class that takes a Point and +Size structure instead of 4 ints. We can add a new file called Rectangle.cs +with a partial class containing our new method: + +public partial class Rectangle +{ + public Rectangle (Point location, Size size) : + this (location.X, location.Y, size.Width, size.Height) + { + } +} + +At compile time, the additions class will be added to the generated class +and the final assembly will a Rectangle class with both constructors. + + +== Adding C# Classes == + +Another thing that can be done is adding fully C# managed classes to the +generated library. In the above example, let's assume that there isn't a +Point class available in Java or our library. The one we create doesn't need +to interact with Java, so we'll create it like a normal class in C#. + +By adding a Point.cs file with this class, it will end up in the binding library: + +public class Point +{ + public int X { get; set; } + public int Y { get; set; } +} \ No newline at end of file diff --git a/src/TestUtils/src/AccessibilityCheck.Droid/Jars/ally.aar b/src/TestUtils/src/AccessibilityCheck.Droid/Jars/ally.aar new file mode 100644 index 000000000000..7ca7ad5f2a7e Binary files /dev/null and b/src/TestUtils/src/AccessibilityCheck.Droid/Jars/ally.aar differ diff --git a/src/TestUtils/src/AccessibilityCheck.Droid/Jars/jsoup-1.15.3.jar b/src/TestUtils/src/AccessibilityCheck.Droid/Jars/jsoup-1.15.3.jar new file mode 100644 index 000000000000..d9024afb8014 Binary files /dev/null and b/src/TestUtils/src/AccessibilityCheck.Droid/Jars/jsoup-1.15.3.jar differ diff --git a/src/TestUtils/src/AccessibilityCheck.Droid/Transforms/EnumFields.xml b/src/TestUtils/src/AccessibilityCheck.Droid/Transforms/EnumFields.xml new file mode 100644 index 000000000000..22959957ec25 --- /dev/null +++ b/src/TestUtils/src/AccessibilityCheck.Droid/Transforms/EnumFields.xml @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/src/TestUtils/src/AccessibilityCheck.Droid/Transforms/EnumMethods.xml b/src/TestUtils/src/AccessibilityCheck.Droid/Transforms/EnumMethods.xml new file mode 100644 index 000000000000..49216c6183c0 --- /dev/null +++ b/src/TestUtils/src/AccessibilityCheck.Droid/Transforms/EnumMethods.xml @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/src/TestUtils/src/AccessibilityCheck.Droid/Transforms/Metadata.xml b/src/TestUtils/src/AccessibilityCheck.Droid/Transforms/Metadata.xml new file mode 100644 index 000000000000..86d4f6520969 --- /dev/null +++ b/src/TestUtils/src/AccessibilityCheck.Droid/Transforms/Metadata.xml @@ -0,0 +1,71 @@ + + + + AccessibilityCheck.Droid.Checks + AccessibilityCheck.Droid + AccessibilityCheck.Droid.Utils.Contrast + AccessibilityCheck.Droid.UIElement + + protected + ACategory + com.google.android.apps.common.testing.accessibility.framework.utils.contrast.Image + AndroidClassName2 + ColorName + com.google.android.apps.common.testing.accessibility.framework.ResultMetadata + ResultMetaDataClone + ResultMetaDataClone + public + not deprecated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/TestUtils/src/AccessibilityCheck.Droid/readme.md b/src/TestUtils/src/AccessibilityCheck.Droid/readme.md new file mode 100644 index 000000000000..541244c8739c --- /dev/null +++ b/src/TestUtils/src/AccessibilityCheck.Droid/readme.md @@ -0,0 +1,3 @@ +## AccessibilityCheck.Droid + +This is .NET wrapper around https://github.com/google/Accessibility-Test-Framework-for-Android, which allows us to run some basic accessibility checks against Android view hierarchies. \ No newline at end of file diff --git a/src/TestUtils/src/DeviceTests.Runners/VisualRunner/Utils/SortedList.cs b/src/TestUtils/src/DeviceTests.Runners/VisualRunner/Utils/SortedList.cs index 8d13e1929cdb..af03fb61d11b 100644 --- a/src/TestUtils/src/DeviceTests.Runners/VisualRunner/Utils/SortedList.cs +++ b/src/TestUtils/src/DeviceTests.Runners/VisualRunner/Utils/SortedList.cs @@ -22,17 +22,7 @@ public int IndexOf(T item) if (Count == 0) return ~0; - for (var i = 0; i < Count; i++) - { - var existing = this[i]; - var compare = _comparer.Compare(item, existing); - if (compare == 0) - return i; - if (compare < 0) - return ~i; - } - - return ~Count; + return _list.BinarySearch(item, _comparer); } public void Insert(int index, T item) diff --git a/src/TestUtils/src/DeviceTests/Accessibility.Android.cs b/src/TestUtils/src/DeviceTests/Accessibility.Android.cs new file mode 100644 index 000000000000..c4059ebc66c3 --- /dev/null +++ b/src/TestUtils/src/DeviceTests/Accessibility.Android.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using System.Text; +using AccessibilityCheck.Droid; +using AccessibilityCheck.Droid.Checks; +using AccessibilityCheck.Droid.UIElement; +using Xunit.Sdk; + +namespace Microsoft.Maui.TestUtils.DeviceTests +{ + public class Accessibility + { + public static void AssertAccessible(Android.Views.View root) + { + List checks = new List() + { + new ClassNameCheck(), + new DuplicateClickableBoundsCheck(), + new DuplicateSpeakableTextCheck(), + new EditableContentDescCheck(), + new RedundantDescriptionCheck(), + new SpeakableTextPresentCheck(), + new TouchTargetSizeCheck(), + }; + + var hierarchy = AccessibilityHierarchyAndroid.NewBuilder(root).Build(); + + var results = RunChecks(hierarchy, checks); + + AssertChecksPassed(results); + } + + static List RunChecks(AccessibilityHierarchy hierarchy, + List checks) + { + List results = new(); + + for(int c = 0; c < checks.Count; c++) + { + var checkResult = checks[c].RunCheckOnHierarchy(hierarchy); + + for(int r = 0; r < checkResult.Count; r++) + { + results.Add(checkResult[r]); + } + } + + return results; + } + + static void AssertChecksPassed(List results) + { + var errors = new List(); + + for (int n = 0; n < results.Count; n++) + { + var result = results[n]; + + if (result.Type == AccessibilityCheckResult.AccessibilityCheckResultType.Error) + { + errors.Add(result); + } + } + + if (errors.Count > 0) + { + throw new XunitException(BuildErrorMessage(errors)); + } + } + + static string BuildErrorMessage(List errors) + { + StringBuilder sb = new StringBuilder(); + + for(int n = 0; n < errors.Count; n++) + { + sb.Append(errors[n].Message); + } + + return sb.ToString(); + } + } +} diff --git a/src/TestUtils/src/DeviceTests/TestUtils.DeviceTests.csproj b/src/TestUtils/src/DeviceTests/TestUtils.DeviceTests.csproj index 1c32bf42140b..862b540a22ad 100644 --- a/src/TestUtils/src/DeviceTests/TestUtils.DeviceTests.csproj +++ b/src/TestUtils/src/DeviceTests/TestUtils.DeviceTests.csproj @@ -23,4 +23,14 @@ + + + + + + + 3.21.1 + + +