Skip to content
1 change: 1 addition & 0 deletions eng/pipelines/ci-official.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ extends:
onlyAndroidPlatformDefaultApis: true
skipAndroidEmulatorImages: true
skipAndroidCreateAvds: true
skipSimulatorSetup: true
skipProvisioning: true
skipXcode: false
base64Encode: true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
using Microsoft.Maui.Handlers;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Automation.Peers;
using Xunit;

namespace Microsoft.Maui.DeviceTests
{
Expand All @@ -17,5 +22,76 @@ static int GetContentChildCount(ContentViewHandler contentViewHandler)
else
return 0;
}

static AutomationPeer GetOrCreateAutomationPeer(ContentPanel contentPanel)
=> new ContentPanel.ContentPanelAutomationPeer(contentPanel);

[Fact(DisplayName = "ContentView With Description Prevents Duplicate Narrator Announcements")]
public async Task ContentViewWithDescriptionHidesChildrenFromNarrator()
{
SetupBuilder();

var label = new Label { Text = "Child Label Text" };
var contentView = new ContentView { Content = label };
SemanticProperties.SetDescription(contentView, "ContentView Description");

await CreateHandlerAndAddToWindow<ContentViewHandler>(contentView, async (handler) =>
{
var contentPanel = handler.PlatformView as ContentPanel;
Assert.NotNull(contentPanel);

// Call UpdateSemantics manually because handler.UpdateValue uses handler.VirtualView
// which doesn't have Semantics populated in the test context
var view = contentView as IView;
contentPanel.UpdateSemantics(view);

var peer = GetOrCreateAutomationPeer(contentPanel);
Assert.NotNull(peer);

var name = Microsoft.UI.Xaml.Automation.AutomationProperties.GetName(contentPanel);
Assert.Equal("ContentView Description", name);

// ControlType should be Text (not Group) to prevent Narrator from saying "group"
var controlType = peer.GetAutomationControlType();
Assert.Equal(AutomationControlType.Text, controlType);

// Children should be hidden to prevent duplicate announcements
var children = peer.GetChildren();
Assert.Null(children);

// LocalizedControlType should be empty to prevent suffix
var localizedControlType = peer.GetLocalizedControlType();
Assert.Equal(string.Empty, localizedControlType);

await Task.CompletedTask;
});
}

[Fact(DisplayName = "ContentView Without Description Shows Children Normally")]
public async Task ContentViewWithoutDescriptionShowsChildren()
{
SetupBuilder();

var label = new Label { Text = "Child Label Text" };
var contentView = new ContentView { Content = label };

await CreateHandlerAndAddToWindow<ContentViewHandler>(contentView, async (handler) =>
{
var contentPanel = handler.PlatformView as ContentPanel;
Assert.NotNull(contentPanel);

var peer = GetOrCreateAutomationPeer(contentPanel);
Assert.NotNull(peer);

var controlType = peer.GetAutomationControlType();
Assert.Equal(AutomationControlType.Custom, controlType);

var children = peer.GetChildren();
Assert.NotNull(children);
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
Assert.NotEmpty(children);

await Task.CompletedTask;
});
}
}
}
22 changes: 22 additions & 0 deletions src/Core/src/Platform/Windows/ContentPanel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Microsoft.Graphics.Canvas;
using Microsoft.Maui.Graphics;
Expand All @@ -9,6 +10,8 @@
#endif
using Microsoft.UI.Composition;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Shapes;

Expand Down Expand Up @@ -73,6 +76,25 @@ public ContentPanel()
SizeChanged += ContentPanelSizeChanged;
}

// Custom automation peer prevents duplicate announcements when AutomationProperties.Name is set
protected override AutomationPeer OnCreateAutomationPeer() => new ContentPanelAutomationPeer(this);

internal partial class ContentPanelAutomationPeer : FrameworkElementAutomationPeer
{
internal ContentPanelAutomationPeer(ContentPanel owner) : base(owner) { }

bool HasDescription => !string.IsNullOrWhiteSpace(AutomationProperties.GetName(Owner));

protected override AutomationControlType GetAutomationControlTypeCore() =>
HasDescription ? AutomationControlType.Text : AutomationControlType.Custom;

protected override string GetLocalizedControlTypeCore() =>
HasDescription ? string.Empty : base.GetLocalizedControlTypeCore() ?? string.Empty;

protected override IList<AutomationPeer>? GetChildrenCore() =>
HasDescription ? null : base.GetChildrenCore();
}

void ContentPanelSizeChanged(object sender, SizeChangedEventArgs e)
{
if (_borderPath is null)
Expand Down
1 change: 1 addition & 0 deletions src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#nullable enable
override Microsoft.Maui.Platform.MauiPasswordTextBox.OnCreateAutomationPeer() -> Microsoft.UI.Xaml.Automation.Peers.AutomationPeer!
override Microsoft.Maui.Platform.ContentPanel.OnCreateAutomationPeer() -> Microsoft.UI.Xaml.Automation.Peers.AutomationPeer!
Loading