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
+
+
+