Skip to content

Commit

Permalink
Merge pull request #17792 from unoplatform/dev/mazi/iseditable
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinZikmund authored Oct 5, 2024
2 parents 8832212 + 366aeef commit 97b0020
Show file tree
Hide file tree
Showing 81 changed files with 12,188 additions and 2,336 deletions.
11 changes: 11 additions & 0 deletions build/PackageDiffIgnore.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1914,6 +1914,17 @@

<Methods>
<Member fullName="System.Void Microsoft.Web.WebView2.Core.CoreWebView2.set_DocumentTitle(System.String value)" reason="Does not exist in UWP" />

<!-- BEGIN ComboBox.IsEditable -->
<Member fullName="System.Void Windows.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean forceFocus, Windows.UI.Xaml.FocusState focusState, System.Boolean animateIfBringIntoView)" reason="Does not exist in UWP" />
<Member fullName="System.Void Windows.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean forceFocus, Windows.UI.Xaml.FocusState focusState, System.Boolean animateIfBringIntoView, Windows.UI.Xaml.Input.FocusNavigationDirection focusNavigationDirection)" reason="Does not exist in UWP" />
<Member fullName="System.Void Windows.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean animateIfBringIntoView, Windows.UI.Xaml.Input.FocusNavigationDirection focusNavigationDirection)" reason="Does not exist in UWP" />
<Member fullName="System.Void Windows.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView)" reason="Does not exist in UWP" />
<Member fullName="System.Void Microsoft.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean forceFocus, Microsoft.UI.Xaml.FocusState focusState, System.Boolean animateIfBringIntoView)" reason="Does not exist in WinUI" />
<Member fullName="System.Void Microsoft.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean forceFocus, Microsoft.UI.Xaml.FocusState focusState, System.Boolean animateIfBringIntoView, Microsoft.UI.Xaml.Input.FocusNavigationDirection focusNavigationDirection)" reason="Does not exist in WinUI" />
<Member fullName="System.Void Microsoft.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView, System.Boolean animateIfBringIntoView, Microsoft.UI.Xaml.Input.FocusNavigationDirection focusNavigationDirection)" reason="Does not exist in WinUI" />
<Member fullName="System.Void Microsoft.UI.Xaml.Controls.Primitives.Selector.SetFocusedItem(System.Int32 index, System.Boolean shouldScrollIntoView)" reason="Does not exist in WinUI" />
<!-- END ComboBox.IsEditable -->
</Methods>
</IgnoreSet>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public partial class Basics_Shapes_Tests : SampleControlUITestBase

[Test]
[AutoRetry]
[ActivePlatforms(Platform.iOS, Platform.Android)]
[ActivePlatforms(Platform.Android)] // Flaky on iOS #9080
[Timeout(TestTimeout)]
public void When_Rectangle()
=> ValidateShape("Rectangle");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ async Task InvokeTestMethod(TestCase testCase)
canRetry = false;
var cleanupActions = new List<Func<Task>>
{
CloseRemainingPopupsAsync
GenericCleanupAsync
};

try
Expand Down Expand Up @@ -976,18 +976,14 @@ await TestServices.WindowHelper.RootElementDispatcher.RunAsync(() =>
}
}

async Task CloseRemainingPopupsAsync()
async Task GenericCleanupAsync()
{
await TestServices.WindowHelper.RootElementDispatcher.RunAsync(() =>
{
var popups = VisualTreeHelper.GetOpenPopupsForXamlRoot(TestServices.WindowHelper.XamlRoot);
if (popups.Count > 0)
{
foreach (var popup in popups)
{
popup.IsOpen = false;
}
}
CloseRemainingPopups();
#if HAS_UNO
ResetLastInputDeviceType();
#endif
});
}

Expand Down Expand Up @@ -1035,6 +1031,35 @@ async ValueTask WaitResult(object returnValue, string step)
}
}

private static void CloseRemainingPopups()
{
var popups = VisualTreeHelper.GetOpenPopupsForXamlRoot(TestServices.WindowHelper.XamlRoot);
if (popups.Count > 0)
{
foreach (var popup in popups)
{
popup.IsOpen = false;
}
}
}

#if HAS_UNO
private static void ResetLastInputDeviceType()
{
// Some tests inject keyboard input, which can then mean that subsequent tests will display
// system focus visuals which are unexpected. This resets the last input device type to mouse.
// Mouse is to stay in line with WinUI integration tests, as some rely on the fact that on
// initial focus of a TextBox the last input device type is not touch (this device type does not
// select all text on initial focus, only on second tap of the input).
// If this is changed ComboBoxIntegrationTests.ValidateTextSubmittedHandledProperty will probably
// start to fail for example.
if (TestServices.WindowHelper.XamlRoot?.VisualTree?.ContentRoot?.InputManager is { } inputManager)
{
inputManager.LastInputDeviceType = Xaml.Input.InputDeviceType.Mouse;
}
}
#endif

private async ValueTask ExecuteOnDispatcher(Func<Task> asyncAction, CancellationToken ct = default)
{
var tcs = new TaskCompletionSource<object>();
Expand Down
187 changes: 187 additions & 0 deletions src/Uno.UI.RuntimeTests/IntegrationTests/common/ComboBoxHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Tests.Enterprise;
using Private.Infrastructure;
using Uno.UI.RuntimeTests.MUX.Helpers;
using Windows.Foundation;
using static Private.Infrastructure.TestServices;

namespace Microsoft.UI.Xaml.Tests.Common;

internal static class ComboBoxHelper
{
public enum OpenMethod
{
Mouse,
Touch,
Keyboard,
Gamepad,
Programmatic
};

public enum CloseMethod
{
Mouse,
Touch,
Keyboard,
Gamepad,
Programmatic
};

public static async Task FocusComboBoxIfNecessary(ComboBox comboBox)
{
var comboBoxGotFocusEvent = new Event();
var gotFocusRegistration = CreateSafeEventRegistration<ComboBox, RoutedEventHandler>("GotFocus");
gotFocusRegistration.Attach(comboBox, (s, e) =>
{
LOG_OUTPUT("[ComboBox]: Got Focus Event Fired.");
comboBoxGotFocusEvent.Set();
});

bool alreadyHasFocus = false;
await RunOnUIThread(() =>
{
alreadyHasFocus = comboBox.FocusState != FocusState.Unfocused;
comboBox.Focus(FocusState.Keyboard);
});

if (!alreadyHasFocus)
{
await comboBoxGotFocusEvent.WaitForDefault();
}
}

public static async Task OpenComboBox(ComboBox comboBox, OpenMethod openMethod)
{
var comboBoxOpenedEvent = new Event();
var openedRegistration = CreateSafeEventRegistration<ComboBox, EventHandler<object>>("DropDownOpened");
openedRegistration.Attach(comboBox, (s, e) => { comboBoxOpenedEvent.Set(); });

if (openMethod == OpenMethod.Mouse)
{
TestServices.InputHelper.LeftMouseClick(comboBox);
}
else if (openMethod == OpenMethod.Touch)
{
TestServices.InputHelper.Tap(comboBox);
}
else if (openMethod == OpenMethod.Keyboard)
{
await FocusComboBoxIfNecessary(comboBox);
await TestServices.KeyboardHelper.PressKeySequence(" ");
}
else if (openMethod == OpenMethod.Gamepad)
{
await FocusComboBoxIfNecessary(comboBox);
await CommonInputHelper.Accept(InputDevice.Gamepad);
}
else if (openMethod == OpenMethod.Programmatic)
{
await RunOnUIThread(() =>
{
comboBox.IsDropDownOpen = true;
});
}
await TestServices.WindowHelper.WaitForIdle();

await comboBoxOpenedEvent.WaitForDefault();
}

public static async Task CloseComboBox(ComboBox comboBox)
{
await CloseComboBox(comboBox, CloseMethod.Programmatic);
}

public static async Task CloseComboBox(ComboBox comboBox, CloseMethod closeMethod)
{
var dropDownClosedEvent = new Event();
var dropDownClosedRegistration = CreateSafeEventRegistration<ComboBox, EventHandler<object>>("DropDownClosed");
dropDownClosedRegistration.Attach(comboBox, (s, e) => { dropDownClosedEvent.Set(); });

if (closeMethod == CloseMethod.Touch || closeMethod == CloseMethod.Mouse)
{
Rect dropdownBounds = await GetBoundsOfOpenDropdown(comboBox);
int outsideBuffer = 10; // Tap at least this far away from the dropdown in order to ensure that it closes.
var closeTapPoint = new Point(dropdownBounds.X + dropdownBounds.Width + outsideBuffer, dropdownBounds.Y + dropdownBounds.Height + outsideBuffer);

if (closeMethod == CloseMethod.Touch)
{
TestServices.InputHelper.Tap(closeTapPoint);
}
else
{
TestServices.InputHelper.LeftMouseClick(closeTapPoint);
}
}
else if (closeMethod == CloseMethod.Keyboard)
{
await CommonInputHelper.Cancel(InputDevice.Keyboard);
}
else if (closeMethod == CloseMethod.Gamepad)
{
await CommonInputHelper.Cancel(InputDevice.Gamepad);
}
else if (closeMethod == CloseMethod.Programmatic)
{
await RunOnUIThread(() =>
{
comboBox.IsDropDownOpen = false;
});
}

await dropDownClosedEvent.WaitForDefault();
await TestServices.WindowHelper.WaitForIdle();

dropDownClosedRegistration.Detach();
}

private static async Task<Rect> GetBoundsOfOpenDropdown(DependencyObject element)
{
Rect dropdownBounds = new();

await RunOnUIThread(async () =>
{
var dropdownScrollViewer = (ScrollViewer)(TreeHelper.GetVisualChildByNameFromOpenPopups("ScrollViewer", element));
Assert.IsNotNull(dropdownScrollViewer, "DropDown not found.");
dropdownBounds = await ControlHelper.GetBounds(dropdownScrollViewer);
LOG_OUTPUT("dropdownBounds: (%f, %f, %f, %f)", dropdownBounds.X, dropdownBounds.Y, dropdownBounds.Width, dropdownBounds.Height);
});
await TestServices.WindowHelper.WaitForIdle();

return dropdownBounds;
}

// Verify the selected Index on the ComboBox.
public static async Task VerifySelectedIndex(ComboBox comboBox, int expected)
{
await SelectorHelper.VerifySelectedIndex(comboBox, expected);
}

// Uses touch to select the ComboBoxItem with the specified index.
// Note: The function does not currently scroll the popup, so it won't work if the item to be selected
// is not immediately visible.
public static async Task SelectItemWithTap(ComboBox comboBox, int index)
{
await OpenComboBox(comboBox, OpenMethod.Touch);
await TestServices.WindowHelper.WaitForIdle();

Event selectionChangedEvent = new();
var selectionChangedRegistration = CreateSafeEventRegistration<ComboBox, SelectionChangedEventHandler>("SelectionChanged");
selectionChangedRegistration.Attach(comboBox, (s, e) => selectionChangedEvent.Set());

ComboBoxItem comboBoxItemToSelect = null;
await RunOnUIThread(() =>
{
comboBoxItemToSelect = (ComboBoxItem)comboBox.ContainerFromIndex(index);
THROW_IF_NULL(comboBoxItemToSelect);
});

TestServices.InputHelper.Tap(comboBoxItemToSelect);
await selectionChangedEvent.WaitForDefault();
}
}
68 changes: 68 additions & 0 deletions src/Uno.UI.RuntimeTests/IntegrationTests/common/FocusTestHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Tests.Enterprise;
using static Private.Infrastructure.TestServices;

namespace Microsoft.UI.Xaml.Tests.Common;

internal static class FocusTestHelper
{
public static async Task EnsureFocus(UIElement element, FocusState focusState, int Attempts = 1)
{
bool gotFocus = false;

while (Attempts > 0 && !gotFocus)
{
Attempts--;

// On desktop, check if there is any other window currently having the window focus
// If so, try to bring the test app to foreground before attempting focus on the element
//if (!Private.Infrastructure.TestServices.Utilities.IsOneCore &&
// !Private.Infrastructure.TestServices.Utilities.IsXBox)
//{
// if (!Private.Infrastructure.TestServices.WindowHelper.IsFocusedWindow)
// {
// LOG_OUTPUT("Test app does not have window focus, bring it to foreground and try again!");
// Private.Infrastructure.TestServices.WindowHelper.RestoreForegroundWindow();
// Private.Infrastructure.TestServices.WindowHelper.WaitForIdle();
// }
//}

var gotFocusEvent = new Event();
// gotFocusEvent MUST be declared before gotFocusRegistration
// Otherwise the event handler could execute after gotFocusEvent has been destroyed.
var gotFocusRegistration = CreateSafeEventRegistration<UIElement, RoutedEventHandler>("GotFocus");

await RunOnUIThread(() =>
{
gotFocusRegistration.Attach(element, (s, e) =>
{
LOG_OUTPUT("Element has received focus.");
gotFocusEvent.Set();
});
if (element.FocusState != FocusState.Unfocused && FocusManager.GetFocusedElement(WindowHelper.WindowContent.XamlRoot).Equals(element))
{
// The element is already focused
LOG_OUTPUT("Focus was already set on this element");
gotFocusEvent.Set();
}
else
{
LOG_OUTPUT("Setting focus to the element...");
element.Focus(focusState);
}
});

await gotFocusEvent.WaitForNoThrow(4000);
gotFocus = gotFocusEvent.HasFired();
}

if (!gotFocus)
{
//Private.Infrastructure.TestServices.Utilities.CaptureScreen("FocusTestHelper");
}
VERIFY_IS_TRUE(gotFocus);
}
}
21 changes: 21 additions & 0 deletions src/Uno.UI.RuntimeTests/IntegrationTests/common/SelectorHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Controls.Primitives;
using static Private.Infrastructure.TestServices;

namespace Microsoft.UI.Xaml.Tests.Common;

internal static class SelectorHelper
{
public static async Task VerifySelectedIndex(Selector selector, int expected)
{
await RunOnUIThread(() =>
{
LOG_OUTPUT("Selected Index = %d", selector.SelectedIndex);
VERIFY_ARE_EQUAL(selector.SelectedIndex, expected);
});
}
}
Loading

0 comments on commit 97b0020

Please sign in to comment.