Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Servicing 3.1] Fixing regression in Control.AccessibleName property #3600

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions eng/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,15 @@ stages:

# Run Integration Tests
# Tests are run with /m:1 to avoid parallelism across different assemblies which can lead to
# UI race conditions
- script: eng\cibuild.cmd
-integrationTest
-configuration $(_BuildConfig)
$(_OfficialBuildIdArgs)
/bl:$(BUILD.SOURCESDIRECTORY)\artifacts\log\$(_BuildConfig)\IntegrationTest.binlog
/m:1
displayName: Run Integration Tests
# UI race conditions
# Flaky tests, see: https://github.com/dotnet/winforms/issues/3617
# - script: eng\cibuild.cmd
# -integrationTest
# -configuration $(_BuildConfig)
# $(_OfficialBuildIdArgs)
# /bl:$(BUILD.SOURCESDIRECTORY)\artifacts\log\$(_BuildConfig)\IntegrationTest.binlog
# /m:1
# displayName: Run Integration Tests

# Create Nuget package, sign, and publish
- script: eng\cibuild.cmd
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>InternalUtilitiesForTests</AssemblyName>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RootNamespace>System</RootNamespace>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace System
{
public static class ReflectionHelper
{
public static IEnumerable<Type> GetPublicNotAbstractClasses<T>()
{
var types = typeof(T).Assembly.GetTypes().Where(type => IsPublicNonAbstract<T>(type));
foreach (var type in types)
{
if (type.GetConstructor(
bindingAttr: BindingFlags.Public | BindingFlags.Instance,
binder: null,
types: Array.Empty<Type>(),
modifiers: null) == null)
{
continue;
}

yield return type ;
}
}

public static T InvokePublicConstructor<T>(Type type)
{
var ctor = type.GetConstructor(
bindingAttr: BindingFlags.Public | BindingFlags.Instance,
binder: null,
types: Array.Empty<Type>(),
modifiers: null);

if (ctor == null)
{
return default;
}

return (T)ctor.Invoke(Array.Empty<object>());
}

public static bool IsPublicNonAbstract<T>(Type type)
{
return !type.IsAbstract && type.IsPublic && typeof(T).IsAssignableFrom(type);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ internal FormAccessibleObject(Form owner) : base(owner)

internal override Rectangle BoundingRectangle => _owner.Bounds;

internal override object GetPropertyValue(int propertyID)
Tanya-Solyanik marked this conversation as resolved.
Show resolved Hide resolved
{
return propertyID == NativeMethods.UIA_NamePropertyId
? Name
: base.GetPropertyValue(propertyID);
}

internal override bool IsIAccessibleExSupported()
{
if (_owner != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,8 @@ internal override object GetPropertyValue(int propertyID)
return NativeMethods.UIA_GroupControlTypeId;
case NativeMethods.UIA_IsKeyboardFocusablePropertyId:
return true;
case NativeMethods.UIA_NamePropertyId:
return Name;
}

return base.GetPropertyValue(propertyID);
Expand Down
7 changes: 5 additions & 2 deletions src/System.Windows.Forms/src/System/Windows/Forms/Label.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1744,9 +1744,12 @@ public override AccessibleRole Role

internal override object GetPropertyValue(int propertyID)
{
if (propertyID == NativeMethods.UIA_ControlTypePropertyId)
switch (propertyID)
{
return NativeMethods.UIA_TextControlTypeId;
case NativeMethods.UIA_ControlTypePropertyId:
return NativeMethods.UIA_TextControlTypeId;
case NativeMethods.UIA_NamePropertyId:
return Name;
}

return base.GetPropertyValue(propertyID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5593,9 +5593,12 @@ internal override UnsafeNativeMethods.IRawElementProviderFragment FragmentNaviga

internal override object GetPropertyValue(int propertyID)
{
if (propertyID == NativeMethods.UIA_ControlTypePropertyId)
switch (propertyID)
{
return NativeMethods.UIA_ToolBarControlTypeId;
case NativeMethods.UIA_ControlTypePropertyId:
return NativeMethods.UIA_ToolBarControlTypeId;
case NativeMethods.UIA_NamePropertyId:
return Name;
}

return base.GetPropertyValue(propertyID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -856,9 +856,12 @@ public ToolStripTextBoxControlAccessibleObject(Control toolStripHostedControl, T

internal override object GetPropertyValue(int propertyID)
{
if (propertyID == NativeMethods.UIA_ControlTypePropertyId)
switch (propertyID)
{
return NativeMethods.UIA_EditControlTypeId;
case NativeMethods.UIA_ControlTypePropertyId:
return NativeMethods.UIA_EditControlTypeId;
case NativeMethods.UIA_NamePropertyId:
return Name;
}

return base.GetPropertyValue(propertyID);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace System.Windows.Forms.Tests.AccessibleObjects
{
public class Control_ControlAccessibleObjectTests
{
public static IEnumerable<object[]> ControlAccessibleObject_IsPatternSupported_LegacyIAccessible_TestData()
{
var supportedLegacyIAccessiblePatternClasses = new List<Type> {
typeof(Form),
typeof(ListBox),
typeof(MonthCalendar),
typeof(PrintPreviewDialog)
};

return ReflectionHelper.GetPublicNotAbstractClasses<Control>()
.Select(type => new object[] { type, supportedLegacyIAccessiblePatternClasses.Contains(type) });
}

[StaTheory]
Tanya-Solyanik marked this conversation as resolved.
Show resolved Hide resolved
[MemberData(nameof(ControlAccessibleObject_IsPatternSupported_LegacyIAccessible_TestData))]
public void ControlAccessibleObject_IsPatternSupported_LegacyIAccessible_ReturnsExpected(Type type, bool legacyIAccessiblePatternSupported)
{
using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

if (control == null)
{
return;
}

AccessibleObject controlAccessibleObject = control.AccessibilityObject;

bool supportsLegacyIAccessiblePatternId = controlAccessibleObject.IsPatternSupported(NativeMethods.UIA_LegacyIAccessiblePatternId);

Assert.Equal(legacyIAccessiblePatternSupported, supportsLegacyIAccessiblePatternId);
}

public static IEnumerable<object[]> ControlAccessibleObject_TestData()
{
return ReflectionHelper.GetPublicNotAbstractClasses<Control>().Select(type => new object[] { type });
}

[StaTheory]
[MemberData(nameof(ControlAccessibleObject_TestData))]
public void ControlAccessibleObject_Custom_Role_ReturnsExpected(Type type)
{
using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

if (control == null)
{
return;
}

control.AccessibleRole = AccessibleRole.Link;
AccessibleObject controlAccessibleObject = control.AccessibilityObject;

var accessibleObjectRole = controlAccessibleObject.Role;

Assert.Equal(AccessibleRole.Link, accessibleObjectRole);
}

[StaTheory]
[MemberData(nameof(ControlAccessibleObject_TestData))]
public void ControlAccessibleObject_Custom_Description_ReturnsExpected(Type type)
{
using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

if (control == null)
{
return;
}

control.AccessibleDescription = "Test Accessible Description";
AccessibleObject controlAccessibleObject = control.AccessibilityObject;

var accessibleObjectDescription = controlAccessibleObject.Description;

Assert.Equal("Test Accessible Description", accessibleObjectDescription);
}

[StaTheory]
[MemberData(nameof(ControlAccessibleObject_TestData))]
public void ControlAccessibleObject_GetPropertyValue_Custom_Name_ReturnsExpected(Type type)
{
using Control control = ReflectionHelper.InvokePublicConstructor<Control>(type);

if (control == null || !control.SupportsUiaProviders)
{
return;
}

control.Name = "Name1";
control.AccessibleName = "Test Name";
Tanya-Solyanik marked this conversation as resolved.
Show resolved Hide resolved
AccessibleObject controlAccessibleObject = control.AccessibilityObject;

var accessibleName = controlAccessibleObject.GetPropertyValue(NativeMethods.UIA_NamePropertyId);

Assert.Equal("Test Name", accessibleName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace System.Windows.Forms.Tests.AccessibleObjects
public class DataGridViewAccessibleObjectTests
{
[Fact]
public void PropertyGridAccessibleObject_Ctor_Default()
public void DataGridViewAccessibleObject_Ctor_Default()
Tanya-Solyanik marked this conversation as resolved.
Show resolved Hide resolved
{
DataGridView dataGridView = new DataGridView();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
Tanya-Solyanik marked this conversation as resolved.
Show resolved Hide resolved
using System.Drawing;
using Moq;
using System.Linq;
using Xunit;

namespace System.Windows.Forms.Tests
Expand All @@ -20,6 +21,7 @@ public void ToolStripItemAccessibleObject_Ctor_ToolStripItem()
AccessibleName = "Name",
AccessibleRole = AccessibleRole.MenuBar
};

var accessibleObject = new ToolStripItem.ToolStripItemAccessibleObject(item);
Assert.Equal(Rectangle.Empty, accessibleObject.Bounds);
Assert.Equal("DefaultActionDescription", accessibleObject.DefaultAction);
Expand All @@ -38,6 +40,63 @@ public void ToolStripItemAccessibleObject_Ctor_NullOwnerItem_ThrowsArgumentNullE
Assert.Throws<ArgumentNullException>("ownerItem", () => new ToolStripItem.ToolStripItemAccessibleObject(null));
}

public static IEnumerable<object[]> ToolStripItemAccessibleObject_TestData()
{
return ReflectionHelper.GetPublicNotAbstractClasses<ToolStripItem>().Select(type => new object[] { type });
}

[Theory]
Tanya-Solyanik marked this conversation as resolved.
Show resolved Hide resolved
[MemberData(nameof(ToolStripItemAccessibleObject_TestData))]
public void ToolStripItemAccessibleObject_Custom_Role_ReturnsExpected(Type type)
{
using ToolStripItem item = ReflectionHelper.InvokePublicConstructor<ToolStripItem>(type);
item.AccessibleRole = AccessibleRole.Link;
Tanya-Solyanik marked this conversation as resolved.
Show resolved Hide resolved
AccessibleObject toolStripItemAccessibleObject = item.AccessibilityObject;

var accessibleObjectRole = toolStripItemAccessibleObject.Role;

Assert.Equal(AccessibleRole.Link, accessibleObjectRole);
}

[WinFormsTheory]
[MemberData(nameof(ToolStripItemAccessibleObject_TestData))]
public void ToolStripItemAccessibleObject_IsPatternSupported_LegacyIAccessible_ReturnsFalse(Type type)
{
using ToolStripItem item = ReflectionHelper.InvokePublicConstructor<ToolStripItem>(type);
AccessibleObject toolStripItemAccessibleObject = item.AccessibilityObject;

bool supportsLegacyIAccessiblePatternId = toolStripItemAccessibleObject.IsPatternSupported(NativeMethods.UIA_LegacyIAccessiblePatternId);

Assert.False(supportsLegacyIAccessiblePatternId);
}

[WinFormsTheory]
[MemberData(nameof(ToolStripItemAccessibleObject_TestData))]
public void ToolStripItemAccessibleObject_Custom_Description_ReturnsExpected(Type type)
{
using ToolStripItem item = ReflectionHelper.InvokePublicConstructor<ToolStripItem>(type);
SergeySmirnov-Akvelon marked this conversation as resolved.
Show resolved Hide resolved
item.AccessibleDescription = "Test Accessible Description";
AccessibleObject toolStripItemAccessibleObject = item.AccessibilityObject;

var accessibleObjectDescription = toolStripItemAccessibleObject.Description;

Assert.Equal("Test Accessible Description", accessibleObjectDescription);
}

[WinFormsTheory]
[MemberData(nameof(ToolStripItemAccessibleObject_TestData))]
public void ToolStripItemAccessibleObject_GetPropertyValue_Custom_Name_ReturnsExpected(Type type)
{
using ToolStripItem item = ReflectionHelper.InvokePublicConstructor<ToolStripItem>(type);
item.Name = "Name1";
item.AccessibleName = "Test Name";

AccessibleObject toolStripItemAccessibleObject = item.AccessibilityObject;
var accessibleName = toolStripItemAccessibleObject.GetPropertyValue(NativeMethods.UIA_NamePropertyId);

Assert.Equal("Test Name", accessibleName);
}

private class SubToolStripItem : ToolStripItem
{
public SubToolStripItem() : base()
Expand Down
Loading