From 1011519b7f5f74a1dceb16da867943205afa1a9f Mon Sep 17 00:00:00 2001 From: Carl de Billy Date: Fri, 28 May 2021 13:08:25 -0400 Subject: [PATCH] test: Added integrations tests from WinUI for CalendarView and CalendarDatePicker --- .../Helpers/DateTimeOffsetExtensions.cs | 12 + .../Helpers/ListExtensions.cs | 18 + .../common/CalendarHelper.h.cs | 431 ++ .../common/CommonTestSetupHelper.cs | 16 + .../IntegrationTests/common/CompareDate.cs | 6 + .../IntegrationTests/common/MockDComp.cs | 10 + .../IntegrationTests/common/PointerFinger.cs | 8 + .../common/TestServices.InputHelper.cs | 46 + .../common/TestServices.KeyboardHelper.cs | 50 + .../common/TestServices.WindowHelper.cs | 266 + .../IntegrationTests/common/TestServices.cs | 332 +- .../dxaml/BaseDxamlTestClass.cs | 91 + .../IntegrationTests/dxaml/Event.cs | 81 + .../dxaml/SafeEventRegistration.cs | 36 + .../CalendarDatePickerHelper.h.cs | 288 + .../CalendarDatePickerIntegrationTests.cs | 955 +++ ...endarViewAutomationPeerIntegrationTests.cs | 743 +++ .../CalendarViewIntegrationTests.cs | 5663 +++++++++++++++++ .../MUX/Helpers/ControlHelper.cs | 15 +- .../MUX/Helpers/FlyoutHelper.cs | 29 + .../MUX/Helpers/TreeHelper.cs | 38 + .../TabView/TabViewTests.cs | 21 +- .../MUX/Utilities/TestHelpers.cs | 2 +- .../CalendarDatePicker.Properties.cs | 5 +- .../Controls/CalendarView/CalendarView_g.cs | 4 +- 25 files changed, 8923 insertions(+), 243 deletions(-) create mode 100644 src/Uno.UI.RuntimeTests/Helpers/DateTimeOffsetExtensions.cs create mode 100644 src/Uno.UI.RuntimeTests/Helpers/ListExtensions.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/common/CalendarHelper.h.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/common/CommonTestSetupHelper.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/common/CompareDate.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/common/MockDComp.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/common/PointerFinger.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.InputHelper.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.KeyboardHelper.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.WindowHelper.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/BaseDxamlTestClass.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/Event.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/SafeEventRegistration.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendardatepicker/CalendarDatePickerHelper.h.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendardatepicker/CalendarDatePickerIntegrationTests.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendarview/CalendarViewAutomationPeerIntegrationTests.cs create mode 100644 src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendarview/CalendarViewIntegrationTests.cs diff --git a/src/Uno.UI.RuntimeTests/Helpers/DateTimeOffsetExtensions.cs b/src/Uno.UI.RuntimeTests/Helpers/DateTimeOffsetExtensions.cs new file mode 100644 index 000000000000..7ba5ee160598 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Helpers/DateTimeOffsetExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace Uno.UI.RuntimeTests.Helpers +{ + internal static class DateTimeOffsetExtensions + { + internal static long UniversalTime(this DateTimeOffset dto) + { + return dto.UtcTicks; + } + } +} diff --git a/src/Uno.UI.RuntimeTests/Helpers/ListExtensions.cs b/src/Uno.UI.RuntimeTests/Helpers/ListExtensions.cs new file mode 100644 index 000000000000..7f51452b1955 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Helpers/ListExtensions.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Uno.UI.RuntimeTests.Helpers +{ + internal static class ListExtensions + { + // Helpers for WinUI code compatibility + internal static T GetAt(this List list, int index) => list[index]; + + internal static T GetAt(this IList list, int index) => list[index]; + + internal static T GetAt(this IReadOnlyList list, int index) => list[index]; + + internal static void SetAt(this IList list, int index, T value) => list[index] = value; + + internal static void Append(this IList list, T item) => list.Add(item); + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/common/CalendarHelper.h.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/common/CalendarHelper.h.cs new file mode 100644 index 000000000000..80b0063c8451 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/common/CalendarHelper.h.cs @@ -0,0 +1,431 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.Globalization; +using Windows.Globalization.DateTimeFormatting; +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Markup; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Tests.Enterprise; +using Uno.UI.RuntimeTests.Helpers; + +using static Private.Infrastructure.TestServices; + +namespace Private.Infrastructure +{ + internal static class CalendarHelper + { + internal static (int era, int year, int month, int day) GetDateFromDateTime(DateTimeOffset datetime) + { + var calendar = new Calendar(); + calendar.SetDateTime(datetime); + + return ( + calendar.Era, + calendar.Year, + calendar.Month, + calendar.Day); + } + + internal static bool CompareDate(DateTimeOffset lhs, DateTimeOffset rhs) + { + var ldate = GetDateFromDateTime(lhs); + var rdate = GetDateFromDateTime(rhs); + + return ldate == rdate; + } + + internal static bool CompareColor(Color lhs, Color rhs) + { + return lhs.A == rhs.A + && lhs.R == rhs.R + && lhs.G == rhs.G + && lhs.B == rhs.B; + } + + internal class DateCollection : List + { + internal int Size => Count; + } + + internal class ColorCollection : List + { + internal int Size => Count; + } + + internal static DependencyObject GetTemplateChild(DependencyObject root, string childName) + { + + var count = VisualTreeHelper.GetChildrenCount(root); + for (var i = 0; i < count; i++) + { + var child = VisualTreeHelper.GetChild(root, i); + var childAsFE = child as FrameworkElement; + if (childAsFE.Name == childName) + { + return child; + } + + var result = GetTemplateChild(child, childName); + if (result != null) + { + return result; + } + } + + return null; + } + + internal static DateTimeOffset ConvertToDateTime( + int era, int year, int month, int day, int period = 1, + int hour = 12, int minute = 0, int second = 0, int nanosecond = 0) + { + var calendar = new Calendar + { + Era = era, + Year = year, + Month = month, + Day = day, + Period = period, + Hour = hour, + Minute = minute, + Second = second, + Nanosecond = nanosecond + }; + return calendar.GetDateTime(); + } + + internal static async Task CreateTestResources() + { + Grid rootPanel = default; + await TestServices.RunOnUIThread(()=> + { + rootPanel = XamlReader.Load( + " ") + as Grid; + + TestServices.WindowHelper.WindowContent = rootPanel; + }); + + return rootPanel; + } + + internal static bool CompareDateTime(DateTimeOffset lhs, DateTimeOffset rhs) + { + return lhs.UniversalTime() == rhs.UniversalTime(); + } + + internal class CalendarViewHelper + { + // public + internal CalendarViewHelper() + { + m_loadedEvent = new Event(); + m_selectedDatesChangedEvent = new Event(); + m_cicEvent = new Event(); + m_addedDates = new DateCollection(); + m_removedDates = new DateCollection(); + m_loadedRegistration = CreateSafeEventRegistration("Loaded"); + m_selectedDatesChangedRegistration = CreateSafeEventRegistration>("SelectedDatesChanged"); + m_cicRegistration = CreateSafeEventRegistration>("CalendarViewDayItemChanging"); + } + + internal async Task GetCalendarView() + { + if (m_cv == null) + { + await TestServices.RunOnUIThread(() => + { + if (m_cv == null) + { + m_cv = new CalendarView(); + TestServices.VERIFY_IS_NOT_NULL(m_cv); + } + }); + } + + + return m_cv; + } + + internal DependencyObject GetTemplateChild(string childName) + { + return GetTemplateChild(m_cv, childName); + } + + internal async Task PrepareLoadedEvent() + { + await TestServices.RunOnUIThread(() => + { + m_loadedRegistration.Attach( + m_cv, + (object sender, RoutedEventArgs e) => + { + OnLoaded(sender, e); + }); + }); + } + + internal async Task PrepareSelectedDatesChangedEvent() + { + m_addedDates.Clear(); + m_removedDates.Clear(); + + await TestServices.RunOnUIThread(() => + { + m_selectedDatesChangedRegistration.Attach( + m_cv, + (sender, e) => + { + OnSelectedDatesChanged(sender, e); + }); + }); + } + + internal void ExpectAddedDate(DateTimeOffset date) + { + m_addedDates.Append(date); + } + + internal void ExpectRemovedDate(DateTimeOffset date) + { + m_removedDates.Append(date); + } + + // note: PrepareCICEvent must be called before item get realized + // the best position is before CalendarView enters visual tree. + internal async Task PrepareCICEvent() + { + await TestServices.RunOnUIThread(() => + { + m_cicRegistration.Attach( + m_cv, + (sender, e) => + { + OnCalendarViewDayItemChanging(sender, e); + }); + }); + } + + internal async Task WaitForLoaded() + { + await m_loadedEvent.WaitForDefault(); + TestServices.VERIFY_IS_TRUE(m_loadedEvent.HasFired()); + m_loadedEvent.Reset(); + m_loadedRegistration.Detach(); + } + + internal async Task WaitForSelectedDatesChanged() + { + await m_selectedDatesChangedEvent.WaitForDefault(); + TestServices.VERIFY_IS_TRUE(m_selectedDatesChangedEvent.HasFired()); + m_selectedDatesChangedEvent.Reset(); + m_selectedDatesChangedRegistration.Detach(); + } + + internal async Task VerifyNoSelectedDatesChanged() + { + // we expect no event here, so below statement will timeout and throw WEX.Common.Exception. + TestServices.VERIFY_THROWS_WINRT( + async () => await m_selectedDatesChangedEvent.WaitFor(TimeSpan.FromMilliseconds(5000), true /* enforceUnderDebugger */), + "SelectedDatesChanged event should not raise!"); + TestServices.VERIFY_IS_FALSE(m_selectedDatesChangedEvent.HasFired()); + m_selectedDatesChangedRegistration.Detach(); + } + + internal async Task WaitForCICEvent() + { + m_cicEvent.WaitForDefault(); + TestServices.VERIFY_IS_TRUE(m_cicEvent.HasFired()); + m_cicEvent.Reset(); + m_cicRegistration.Detach(); + } + + // private + void OnLoaded(object sender, RoutedEventArgs e) + { + TestServices.LOG_OUTPUT("CalendarViewIntegrationTests: CalendarView Loaded."); + m_loadedEvent.Set(); + } + + void OnSelectedDatesChanged(object sender, + CalendarViewSelectedDatesChangedEventArgs e) + { + TestServices.VERIFY_ARE_EQUAL(e.AddedDates.Count, m_addedDates.Count); + TestServices.VERIFY_ARE_EQUAL(e.RemovedDates.Count, m_removedDates.Count); + + for (var i = 0; i < e.AddedDates.Count; ++i) + { + TestServices.VERIFY_ARE_EQUAL(e.AddedDates.GetAt(i).UniversalTime(), m_addedDates.GetAt(i).UniversalTime()); + } + + for (var i = 0; i < e.RemovedDates.Count; ++i) + { + TestServices.VERIFY_ARE_EQUAL(e.RemovedDates.GetAt(i).UniversalTime(), m_removedDates.GetAt(i).UniversalTime()); + } + + m_selectedDatesChangedEvent.Set(); + } + + void OnCalendarViewDayItemChanging(object sender, + CalendarViewDayItemChangingEventArgs e) + { + // phase 2: set density bar + // phase 5: blackout + // phase 7: end cic event + + if (e.Phase == 2) + { + ColorCollection colors = new ColorCollection(); + colors.Append(Colors.Red); + colors.Append(Colors.Green); + colors.Append(Colors.Blue); + colors.Append(Colors.Yellow); + e.Item.SetDensityColors(colors); + } + else if (e.Phase == 5) + { + e.Item.IsBlackout = true; + } + else if (e.Phase == 7) + { + m_cicEvent.Set(); + } + + // keep subscribing cic event until phase 7. + if (e.Phase < 7) + { + e.RegisterUpdateCallback( + (sender, e) => + { + OnCalendarViewDayItemChanging(sender, e); + }); + } + } + + DependencyObject GetTemplateChild(DependencyObject root, string childName) + { + var count = VisualTreeHelper.GetChildrenCount(root); + for (var i = 0; i < count; i++) + { + var child = VisualTreeHelper.GetChild(root, i); + var childAsFE = child as FrameworkElement; + if (childAsFE.Name == childName) + { + return child; + } + + var result = GetTemplateChild(child, childName); + if (result != null) + { + return result; + } + } + + return null; + } + + // private + CalendarView m_cv; + + SafeEventRegistration m_loadedRegistration; + + SafeEventRegistration> m_selectedDatesChangedRegistration; + + SafeEventRegistration> m_cicRegistration; + + Event m_loadedEvent; + Event m_selectedDatesChangedEvent; + Event m_cicEvent; + DateCollection m_addedDates; + DateCollection m_removedDates; + }; + + + internal static void DumpDate(DateTimeOffset date, string prefix) + { + var calendar = new Calendar(); + calendar.SetDateTime(date); + TestServices.LOG_OUTPUT("%s: %d/%d/%d (dd/mm/yy)", prefix, calendar.Year, calendar.Month, calendar.Day); + } + + internal static void VerifyDateTimesAreEqual(DateTimeOffset date1, DateTimeOffset date2) + { + if (date1.UniversalTime() != date2.UniversalTime()) + { + var calendar = new Calendar(); + calendar.SetDateTime(date1); + TestServices.LOG_OUTPUT("UTC %lld: %d/%d/%d %d:%d:%d %d", date1.UniversalTime(), calendar.Year, calendar.Month, + calendar.Day, calendar.Hour, calendar.Minute, calendar.Second, calendar.Nanosecond); + calendar.SetDateTime(date2); + TestServices.LOG_OUTPUT("UTC %lld: %d/%d/%d %d:%d:%d %d", date2.UniversalTime(), calendar.Year, calendar.Month, + calendar.Day, calendar.Hour, calendar.Minute, calendar.Second, calendar.Nanosecond); + TestServices.VERIFY_ARE_EQUAL(date1.UniversalTime(), date2.UniversalTime()); + } + } + + internal static void CheckFocusedItem() + { + var item = FocusManager.GetFocusedElement(TestServices.WindowHelper.WindowContent.XamlRoot); + TestServices.LOG_OUTPUT("Type of focused item is: %s", item.GetType().FullName); + var itemAsFE = (FrameworkElement)(item); + if (itemAsFE is { }) + { + var point = itemAsFE.TransformToVisual(null).TransformPoint(new Point(0, 0)); + TestServices.LOG_OUTPUT("Focused item position. x %f, y %f, width %f, height %f", point.X, point.Y, + itemAsFE.ActualWidth, itemAsFE.ActualHeight); + } + + var itemAsDayItem = + (CalendarViewDayItem)(item); + if (itemAsDayItem is { }) + { + TestServices.LOG_OUTPUT("Focused item is a day item, date is %s", + DateTimeFormatter.ShortDate.Format(itemAsDayItem.Date) + ); + } + } + + internal static bool AreClose(double a, double b, double threshold = 0.1) + { + TestServices.LOG_OUTPUT("AreClose? %lf, %lf", a, b); + return Math.Abs(a - b) <= threshold; + } + + internal static void ReplaceAccentColorForTesting(CalendarView cv) + { + // replace the accent colors to some color ants to make sure test results are not affected by the accent color. + cv.TodayForeground = new SolidColorBrush(Colors.Red); + cv.SelectedBorderBrush = new SolidColorBrush(Colors.Red); + cv.SelectedPressedBorderBrush = new SolidColorBrush(Colors.Green); + cv.SelectedHoverBorderBrush = new SolidColorBrush(Colors.Blue); + } + + + internal static string[] GetAllSupportedCalendarIdentifiers() + { + return new [] + { + "PersianCalendar", + "GregorianCalendar", + "HebrewCalendar", + "HijriCalendar", + "JapaneseCalendar", + "JulianCalendar", + "KoreanCalendar", + "TaiwanCalendar", + "ThaiCalendar", + "UmAlQuraCalendar" + }; + } + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/common/CommonTestSetupHelper.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/common/CommonTestSetupHelper.cs new file mode 100644 index 000000000000..e1067c36f1f7 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/common/CommonTestSetupHelper.cs @@ -0,0 +1,16 @@ +namespace Private.Infrastructure +{ + internal static class CommonTestSetupHelper + { + private static bool _isInitialized = false; + + internal static async void CommonTestClassSetup() + { + if (!_isInitialized) + { + TestServices.EnsureInitialized(); + } + _isInitialized = true; + } + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/common/CompareDate.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/common/CompareDate.cs new file mode 100644 index 000000000000..db4a24d53a5f --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/common/CompareDate.cs @@ -0,0 +1,6 @@ +using System; + +namespace Private.Infrastructure +{ + public delegate bool CompareDate(DateTimeOffset dto1, DateTimeOffset dto2); +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/common/MockDComp.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/common/MockDComp.cs new file mode 100644 index 000000000000..e53fcc0fcb71 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/common/MockDComp.cs @@ -0,0 +1,10 @@ +namespace Private.Infrastructure +{ + public class MockDComp + { + public enum SurfaceComparison + { + NoComparison + } + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/common/PointerFinger.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/common/PointerFinger.cs new file mode 100644 index 000000000000..402025240354 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/common/PointerFinger.cs @@ -0,0 +1,8 @@ +namespace Private.Infrastructure +{ + public enum PointerFinger + { + Finger1, + Finger2 + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.InputHelper.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.InputHelper.cs new file mode 100644 index 000000000000..434feff69982 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.InputHelper.cs @@ -0,0 +1,46 @@ +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Private.Infrastructure +{ + partial class TestServices + { + public static class InputHelper + { + public static void DynamicPressCenter(UIElement element, double x, double y, PointerFinger finger) + { + throw new System.NotImplementedException(); + } + public static void DynamicRelease(PointerFinger finger) + { + throw new System.NotImplementedException(); + } + + public static void MoveMouse(Point position) + { + throw new System.NotImplementedException(); + } + + public static void MoveMouse(UIElement element) + { + throw new System.NotImplementedException(); + } + + public static void Tap(UIElement element) + { + throw new System.NotImplementedException(); + } + + public static void ScrollMouseWheel(CalendarView cv, int i) + { + throw new System.NotImplementedException(); + } + + public static void LeftMouseClick(UIElement element) + { + throw new System.NotImplementedException(); + } + } + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.KeyboardHelper.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.KeyboardHelper.cs new file mode 100644 index 000000000000..5b599ed8abee --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.KeyboardHelper.cs @@ -0,0 +1,50 @@ +using System; + +namespace Private.Infrastructure +{ + public partial class TestServices + { + public static class KeyboardHelper + { + public static void PressKeySequence(string keys) + { + throw new System.NotImplementedException(); + } + + public static void Down() + { + throw new System.NotImplementedException(); + } + + public static void Up() + { + throw new System.NotImplementedException(); + } + + internal static void ShiftTab() + { + throw new NotImplementedException(); + } + + public static void Tab() + { + throw new NotImplementedException(); + } + + public static void PageDown() + { + throw new NotImplementedException(); + } + + public static void Escape() + { + throw new NotImplementedException(); + } + + public static void Enter() + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.WindowHelper.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.WindowHelper.cs new file mode 100644 index 000000000000..bde9e2173627 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.WindowHelper.cs @@ -0,0 +1,266 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using System.Runtime.CompilerServices; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Tests.Enterprise; +#if NETFX_CORE +using Uno.UI.Extensions; +#elif __IOS__ +using UIKit; +#elif __MACOS__ +using AppKit; +#else +using Uno.UI; +#endif + +namespace Private.Infrastructure +{ + public partial class TestServices + { + public static class WindowHelper + { + public static UIElement WindowContent + { + get => RootControl.Content as UIElement; + internal set + { + if (RootControl is ContentControl content) + { + content.Content = value; + } + else + { + Console.WriteLine("Failed to get test content control"); + } + } + } + + public static ContentControl RootControl { get; set; } + + internal static async Task WaitForIdle() + { + await RootControl.Dispatcher.RunIdleAsync(_ => { /* Empty to wait for the idle queue to be reached */ }); + await RootControl.Dispatcher.RunIdleAsync(_ => { /* Empty to wait for the idle queue to be reached */ }); + } + + /// + /// Waits for to be loaded and measured in the visual tree. + /// + /// + /// On UWP, may not always wait long enough for the control to be properly measured. + /// + /// This method assumes that the control will have a non-zero size once loaded, so it's not appropriate for elements that are + /// collapsed, empty, etc. + /// + internal static async Task WaitForLoaded(FrameworkElement element) + { + await WaitFor(IsLoaded, message: $"{element} loaded"); + bool IsLoaded() + { + if (element.ActualHeight == 0 || element.ActualWidth == 0) + { + return false; + } + + if (element is Control control && control.FindFirstChild(includeCurrent: false) == null) + { + return false; + } + + return true; + } + } + + internal static async Task WaitForRelayouted(FrameworkElement frameworkElement) + { + var isRelayouted = false; + + void OnLayoutUpdated(object _, object __) + { + frameworkElement.LayoutUpdated -= OnLayoutUpdated; + isRelayouted = true; + } + + frameworkElement.LayoutUpdated += OnLayoutUpdated; + + await WaitFor(() => isRelayouted, message: $"{frameworkElement} re-layouted"); + } + + internal static async Task WaitForEqual(double expected, Func actualFunc, double tolerance = 1.0, int timeoutMS = 1000) + { + if (actualFunc is null) + { + throw new ArgumentNullException(nameof(actualFunc)); + } + + var actual = actualFunc(); + if (ApproxEquals(actual)) + { + return; + } + + var stopwatch = Stopwatch.StartNew(); + while (stopwatch.ElapsedMilliseconds < timeoutMS) + { + await WaitForIdle(); + actual = actualFunc(); + if (ApproxEquals(actual)) + { + return; + } + } + + throw new AssertFailedException($"Timed out waiting for equality condition to be met. Expected {expected} but last received value was {actual}."); + + bool ApproxEquals(double actualValue) => Math.Abs(expected - actualValue) < tolerance; + } + + /// + /// Wait until a specified is met. + /// + /// The maximum time to wait before failing the test, in milliseconds. + internal static async Task WaitFor(Func condition, int timeoutMS = 1000, string message = null, [CallerMemberName] string callerMemberName = null, [CallerLineNumber] int lineNumber = 0) + { + if (condition()) + { + return; + } + + var stopwatch = Stopwatch.StartNew(); + while (stopwatch.ElapsedMilliseconds < timeoutMS) + { + await WaitForIdle(); + if (condition()) + { + return; + } + } + + message ??= $"{callerMemberName}():{lineNumber}"; + + throw new AssertFailedException("Timed out waiting for condition to be met. " + message); + } + + internal static async Task WaitFor( + Func condition, + T expected, + Func messageBuilder = null, + Func comparer = null, + int timeoutMS = 1000, + [CallerMemberName] string callerMemberName = null, + [CallerLineNumber] int lineNumber = 0) + { + comparer ??= (v1, v2) => Equals(v1, v2); + + T value = condition(); + if (comparer(value, expected)) + { + return; + } + + var stopwatch = Stopwatch.StartNew(); + while (stopwatch.ElapsedMilliseconds < timeoutMS) + { + await WaitForIdle(); + value = condition(); + if (comparer(value, expected)) + { + return; + } + } + + var customMsg = messageBuilder != null ? messageBuilder(value) : $"Got {value}, expected {expected}"; + var message = $"{callerMemberName}():{lineNumber} {customMsg}"; + + throw new AssertFailedException("Timed out waiting for condition to be met. " + message); + } + + internal static async Task WaitForNonNull( + Func getT, + int timeoutMS = 1000, + string message = null, + [CallerMemberName] string callerMemberName = null, + [CallerLineNumber] int lineNumber = 0) + where T : class + { + if (getT is null) + { + throw new ArgumentNullException(nameof(getT)); + } + + if (getT() is { } t) + { + return t; + } + + var stopwatch = Stopwatch.StartNew(); + while (stopwatch.ElapsedMilliseconds < timeoutMS) + { + await WaitForIdle(); + + + if (getT() is { } t2) + { + return t2; + } + } + + message ??= $"{callerMemberName}():{lineNumber} Never received non-null value"; + + throw new AssertFailedException("Timed out waiting for condition to be met. " + message); + } + +#if DEBUG + /// + /// This will wait. Forever. Useful when debugging a runtime test if you wish to visually inspect or interact with a view added + /// by the test. (To break out of the loop, just set 'shouldWait = false' via the Immediate Window.) + /// + internal static async Task WaitForever() + { + var shouldWait = true; + while (shouldWait) + { + await Task.Delay(1000); + } + } +#endif + + internal static void ShutdownXaml() { } + internal static void VerifyTestCleanup() { } + + internal static void SetWindowSizeOverride(object p) { } + + private static readonly TimeSpan FiveMinutes = TimeSpan.FromMinutes(5); + + public static async Task SynchronouslyTickUIThread(int ticks) + { + var tickCompleteEvent = new Event(); + + for (var i = 0; i < ticks; i++) + { + await RunOnUIThread(() => + { + CompositionTarget.Rendering += OnCompositionTargetOnRendering; + }); + + void OnCompositionTargetOnRendering(object sender, object o) + { + CompositionTarget.Rendering -= OnCompositionTargetOnRendering; + tickCompleteEvent.Set(); + } + + await tickCompleteEvent.WaitFor(FiveMinutes); + } + } + + internal static void ResetWindowContentAndWaitForIdle() + { + + } + } + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.cs index d265e15ab972..cf5b7184ccea 100644 --- a/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.cs +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/common/TestServices.cs @@ -1,242 +1,33 @@ using System; -using System.Diagnostics; +using System.Globalization; using System.Threading.Tasks; +using Windows.UI.Xaml.Tests.Enterprise; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using System.Runtime.CompilerServices; -#if NETFX_CORE -using Uno.UI.Extensions; -#elif __IOS__ -using UIKit; -#elif __MACOS__ -using AppKit; -#else -using Uno.UI; -#endif +using FluentAssertions; namespace Private.Infrastructure { - public class TestServices + public static partial class TestServices { - public class WindowHelper - { - public static object WindowContent - { - get => RootControl.Content; - internal set - { - if (RootControl is ContentControl content) - { - content.Content = value; - } - else - { - Console.WriteLine("Failed to get test content control"); - } - } - } - - public static ContentControl RootControl { get; set; } - - internal static async Task WaitForIdle() - { - await RootControl.Dispatcher.RunIdleAsync(_ => { /* Empty to wait for the idle queue to be reached */ }); - await RootControl.Dispatcher.RunIdleAsync(_ => { /* Empty to wait for the idle queue to be reached */ }); - } - /// - /// Waits for to be loaded and measured in the visual tree. - /// - /// - /// On UWP, may not always wait long enough for the control to be properly measured. - /// - /// This method assumes that the control will have a non-zero size once loaded, so it's not appropriate for elements that are - /// collapsed, empty, etc. - /// - internal static async Task WaitForLoaded(FrameworkElement element) - { - await WaitFor(IsLoaded, message: $"{element} loaded"); - bool IsLoaded() - { - if (element.ActualHeight == 0 || element.ActualWidth == 0) - { - return false; - } - - if (element is Control control && control.FindFirstChild(includeCurrent: false) == null) - { - return false; - } - - return true; - } - } - - internal static async Task WaitForRelayouted(FrameworkElement frameworkElement) - { - var isRelayouted = false; - - void OnLayoutUpdated(object _, object __) - { - frameworkElement.LayoutUpdated -= OnLayoutUpdated; - isRelayouted = true; - } - - frameworkElement.LayoutUpdated += OnLayoutUpdated; - - await WaitFor(() => isRelayouted, message: $"{frameworkElement} re-layouted"); - } - - internal static async Task WaitForEqual(double expected, Func actualFunc, double tolerance = 1.0, int timeoutMS = 1000) - { - if (actualFunc is null) - { - throw new ArgumentNullException(nameof(actualFunc)); - } - - var actual = actualFunc(); - if (ApproxEquals(actual)) - { - return; - } - - var stopwatch = Stopwatch.StartNew(); - while (stopwatch.ElapsedMilliseconds < timeoutMS) - { - await WaitForIdle(); - actual = actualFunc(); - if (ApproxEquals(actual)) - { - return; - } - } - - throw new AssertFailedException($"Timed out waiting for equality condition to be met. Expected {expected} but last received value was {actual}."); - - bool ApproxEquals(double actualValue) => Math.Abs(expected - actualValue) < tolerance; - } + public class Utilities + { + internal static void VerifyMockDCompOutput(MockDComp.SurfaceComparison comparison, string step) { } + internal static void VerifyMockDCompOutput(MockDComp.SurfaceComparison comparison) { } - /// - /// Wait until a specified is met. - /// - /// The maximum time to wait before failing the test, in milliseconds. - internal static async Task WaitFor(Func condition, int timeoutMS = 1000, string message = null, [CallerMemberName] string callerMemberName = null, [CallerLineNumber] int lineNumber = 0) + internal static void VerifyUIElementTree() { - if (condition()) - { - return; - } - - var stopwatch = Stopwatch.StartNew(); - while (stopwatch.ElapsedMilliseconds < timeoutMS) - { - await WaitForIdle(); - if (condition()) - { - return; - } - } - - message ??= $"{callerMemberName}():{lineNumber}"; - - throw new AssertFailedException("Timed out waiting for condition to be met. " + message); } - internal static async Task WaitFor( - Func condition, - T expected, - Func messageBuilder = null, - Func comparer = null, - int timeoutMS = 1000, - [CallerMemberName] string callerMemberName = null, - [CallerLineNumber] int lineNumber = 0) + public static void SetTimeZone(string tzid) { - comparer ??= (v1, v2) => Equals(v1, v2); - - T value = condition(); - if (comparer(value, expected)) - { - return; - } - - var stopwatch = Stopwatch.StartNew(); - while (stopwatch.ElapsedMilliseconds < timeoutMS) - { - await WaitForIdle(); - value = condition(); - if (comparer(value, expected)) - { - return; - } - } - - var customMsg = messageBuilder != null ? messageBuilder(value) : $"Got {value}, expected {expected}"; - var message = $"{callerMemberName}():{lineNumber} {customMsg}"; - - throw new AssertFailedException("Timed out waiting for condition to be met. " + message); + throw new NotImplementedException(); } - internal static async Task WaitForNonNull( - Func getT, - int timeoutMS = 1000, - string message = null, - [CallerMemberName] string callerMemberName = null, - [CallerLineNumber] int lineNumber = 0) - where T : class + internal static void EnableChangingTimeZone(bool isEnabled) { - if (getT is null) - { - throw new ArgumentNullException(nameof(getT)); - } - if (getT() is { } t) - { - return t; - } - - var stopwatch = Stopwatch.StartNew(); - while (stopwatch.ElapsedMilliseconds < timeoutMS) - { - await WaitForIdle(); - - - if (getT() is { } t2) - { - return t2; - } - } - - message ??= $"{callerMemberName}():{lineNumber} Never received non-null value"; - - throw new AssertFailedException("Timed out waiting for condition to be met. " + message); } - -#if DEBUG - /// - /// This will wait. Forever. Useful when debugging a runtime test if you wish to visually inspect or interact with a view added - /// by the test. (To break out of the loop, just set 'shouldWait = false' via the Immediate Window.) - /// - internal static async Task WaitForever() - { - var shouldWait = true; - while (shouldWait) - { - await Task.Delay(1000); - } - } -#endif - - internal static void ShutdownXaml() { } - internal static void VerifyTestCleanup() { } - - internal static void SetWindowSizeOverride(object p) { } - } - - public class Utilities - { - internal static void VerifyMockDCompOutput(MockDComp.SurfaceComparison comparison, string step) { } - internal static void VerifyMockDCompOutput(MockDComp.SurfaceComparison comparison) { } } internal static async Task RunOnUIThread(Action action) @@ -250,6 +41,31 @@ internal static async Task RunOnUIThread(Action action) internal static void EnsureInitialized() { } + public static void VERIFY_IS_NOT_NULL(object value) + { + Assert.IsNotNull(value); + } + + public static void VERIFY_IS_NULL(object value) + { + Assert.IsNull(value); + } + + public static void THROW_IF_NULL_WITH_MSG(object value, string msg) + { + Assert.IsNotNull(value, msg); + } + + public static void VERIFY_IS_TRUE(bool value) + { + Assert.IsTrue(value); + } + + public static void VERIFY_IS_FALSE(bool value) + { + Assert.IsFalse(value); + } + public static void VERIFY_IS_LESS_THAN(double actual, double expected) { Assert.IsTrue(actual < expected, $"{actual} is not less than {expected}"); @@ -271,9 +87,28 @@ public static void VERIFY_THROWS_WINRT(Action action, Type exceptionType) } } + public static void VERIFY_THROWS_WINRT(Action action, string text) + where TExpectedException : Exception + { + var exceptionType = typeof(TExpectedException); + try + { + action(); + Assert.Fail($"Exception of type {exceptionType} is expected: {text}"); + } + catch (Exception e) + { + if (e.GetType() != exceptionType) + { + Assert.Fail($"Exception of type {exceptionType} is expected (was {e.GetType()}. {text}"); + } + } + } + internal static void VERIFY_ARE_EQUAL(T actual, T expected) { - Assert.AreEqual(expected: expected, actual: actual); + //Assert.AreEqual(expected: expected, actual: actual); + actual­.Should().Be(expected); } internal static void VERIFY_ARE_VERY_CLOSE(double actual, double expected, double tolerance = 0.1d) @@ -281,13 +116,56 @@ internal static void VERIFY_ARE_VERY_CLOSE(double actual, double expected, doubl var difference = Math.Abs(actual - expected); Assert.IsTrue(difference <= tolerance, $"Expected <{expected}>, actual <{actual}> (tolerance = {tolerance})"); } - } - public class MockDComp - { - public enum SurfaceComparison + internal static void LOG_OUTPUT(string log, params object[] arguments) + { + if (arguments != null && arguments.Length != 0) + { + var offset = 0; + for (var i = 0;; i++) + { + offset = log.IndexOf('%', offset + 1); + if (offset < 0 || offset + 1 >= log.Length) + { + break; // finished + } + + string replacement = default; + + switch(log[offset + 1]) + { + case 's': + case 'd': + case 'f': + case 'i': + case 'g': + replacement = $"{{{i}}}"; + break; + case 'x': + replacement = $"{{{i}:x}}"; + break; + case 'X': + replacement = $"{{{i}:X}}"; + break; + } + + if (replacement is { }) + { + log = log.Substring(0, offset) + replacement + log.Substring(offset + 2); + } + } + + log = string.Format(CultureInfo.InvariantCulture, log, args: arguments); + } + + Console.WriteLine(log); + } + + internal static SafeEventRegistration CreateSafeEventRegistration(string eventName) + where T : class + where TDelegate : Delegate { - NoComparison + return new SafeEventRegistration(eventName); } } } diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/BaseDxamlTestClass.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/BaseDxamlTestClass.cs new file mode 100644 index 000000000000..82b11b0d812b --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/BaseDxamlTestClass.cs @@ -0,0 +1,91 @@ +using System; +using System.Threading.Tasks; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Media; +using Private.Infrastructure; + +namespace Windows.UI.Xaml.Tests.Enterprise +{ + public abstract class BaseDxamlTestClass + { + + internal SafeEventRegistration CreateSafeEventRegistration(string eventName) + where TElement : class + where TDelegate : Delegate + { + return new SafeEventRegistration(eventName); + } + + protected static Task RunOnUIThread(Action action) + { + return TestServices.RunOnUIThread(action); + } + + protected static void VERIFY_IS_TRUE(bool value) + { + TestServices.VERIFY_IS_TRUE(value); + } + + protected static void VERIFY_IS_FALSE(bool value) + { + TestServices.VERIFY_IS_FALSE(value); + } + + protected static void VERIFY_ARE_EQUAL(T actual, T expected) + { + TestServices.VERIFY_ARE_EQUAL(actual, expected); + } + + protected static void VERIFY_IS_NULL(object value) + { + TestServices.VERIFY_IS_NULL(value); + } + + protected static void LOG_OUTPUT(string log, params object[] arguments) + { + TestServices.LOG_OUTPUT(log, arguments); + } + + protected static void VERIFY_THROWS_WINRT(Action action, Type exceptionType) + { + TestServices.VERIFY_THROWS_WINRT(action, exceptionType); + } + + protected static void VERIFY_THROWS_WINRT(Action action, string text) + where TExpectedException : Exception + { + TestServices.VERIFY_THROWS_WINRT(action, text); + } + + protected static int ARRAYSIZE(Array array) + { + return array.Length; + } + + protected static FrameworkElement FrameworkElement(object o) => o as FrameworkElement; + protected static Control Control(object o) => o as Control; + protected static Grid Grid(object o) => o as Grid; + protected static Flyout Flyout(object o) => o as Flyout; + protected static StackPanel StackPanel(object o) => o as StackPanel; + protected static ScrollViewer ScrollViewer(object o) => o as ScrollViewer; + protected static Border Border(object o) => o as Border; + protected static Button Button(object o) => o as Button; + protected static TextBlock TextBlock(object o) => o as TextBlock; + protected static string String(object o) => o as string; + protected static SolidColorBrush SolidColorBrush(object o) => o as SolidColorBrush; + + protected static CalendarView CalendarView(object o) => o as CalendarView; + protected static CalendarDatePicker CalendarDatePicker(object o) => o as CalendarDatePicker; + protected static CalendarPanel CalendarPanel(object o) => o as CalendarPanel; + protected static CalendarViewDayItem CalendarViewDayItem(object o) => o as CalendarViewDayItem; + } + + internal class XcbPurpleBrush : SolidColorBrush + { + internal XcbPurpleBrush() + { + Color = Colors.Purple; + } + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/Event.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/Event.cs new file mode 100644 index 000000000000..d4af03186519 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/Event.cs @@ -0,0 +1,81 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace Windows.UI.Xaml.Tests.Enterprise +{ + internal class Event + { + private readonly ManualResetEventSlim _event = new ManualResetEventSlim(); + + internal int FiredCount { get; private set; } + + internal async Task WaitForDefault(int timeout = 5000, CancellationToken ct = default) + { + var h = _event.WaitHandle; + RegisteredWaitHandle registration = default; + try + { + if (h.WaitOne(0)) + { + return true; + } + + if (timeout == 0) + { + return false; + } + + var tcs = new TaskCompletionSource(); + + using var _ = ct.Register(() => tcs.TrySetCanceled()); + + registration = ThreadPool + .RegisterWaitForSingleObject( + h, + (_, __) => + { + tcs.TrySetResult(true); + }, + null, + timeout, + executeOnlyOnce: true); + + return await tcs.Task; + } + finally + { + registration?.Unregister(h); + } + } + + internal void Set() + { + FiredCount++; + _event.Set(); + } + + internal bool HasFired() => _event.IsSet; + + public void Reset() + { + _event.Reset(); + } + + public Task WaitFor(TimeSpan timeout, CancellationToken ct = default) + { + return WaitForDefault((int)timeout.TotalMilliseconds, ct); + } + + internal Task WaitFor(TimeSpan timeout, bool enforceUnderDebugger, CancellationToken ct = default) + { + if (!enforceUnderDebugger && Debugger.IsAttached) + { + return Task.CompletedTask; + } + + return WaitForDefault((int)timeout.TotalMilliseconds, ct); + } + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/SafeEventRegistration.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/SafeEventRegistration.cs new file mode 100644 index 000000000000..190d39aba34d --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/SafeEventRegistration.cs @@ -0,0 +1,36 @@ +#nullable enable +using System; +using System.Reflection; +using System.Reflection.Emit; +using Windows.UI.Xaml.Controls; +using Uno.Disposables; + +namespace Windows.UI.Xaml.Tests.Enterprise +{ + internal class SafeEventRegistration + where TElement : class + where TDelegate : Delegate + { + private readonly EventInfo _eventInfo; + private IDisposable? _last; + + public SafeEventRegistration(string eventName) + { + _eventInfo = typeof(TElement).GetEvent(eventName); + } + + internal IDisposable Attach(TElement element, TDelegate handler) + { + Detach(); // Detach any previous handler + + _eventInfo.GetAddMethod().Invoke(element, new object[] {handler}); + + return _last = Disposable.Create(() => + { + _eventInfo.GetRemoveMethod().Invoke(element, new object[] {handler}); + }); + } + + internal void Detach() => _last?.Dispose(); + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendardatepicker/CalendarDatePickerHelper.h.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendardatepicker/CalendarDatePickerHelper.h.cs new file mode 100644 index 000000000000..56e5a942679d --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendardatepicker/CalendarDatePickerHelper.h.cs @@ -0,0 +1,288 @@ +// 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 Windows.Foundation; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Tests.Enterprise; +using Uno.UI.RuntimeTests.Helpers; + +using static Private.Infrastructure.TestServices; +using static Private.Infrastructure.CalendarHelper; +using Private.Infrastructure; + +namespace Windows.UI.Xaml.Tests.Common +{ + + internal class CalendarDatePickerHelper + { + // public + internal CalendarDatePickerHelper() + { + m_loadedEvent = new Event(); + m_selectedDatesChangedEvent = new Event(); + m_cicEvent = new Event(); + m_openedEvent = new Event(); + m_closedEvent = new Event(); + m_addedDates = new CalendarHelper.DateCollection(); + m_removedDates = new CalendarHelper.DateCollection(); + m_loadedRegistration = CreateSafeEventRegistration("Loaded"); + m_selectedDatesChangedRegistration = CreateSafeEventRegistration>("DateChanged"); + m_cicRegistration = CreateSafeEventRegistration("CalendarViewDayItemChanging"); + m_openedRegistration = CreateSafeEventRegistration>("Opened"); + m_closedRegistration = CreateSafeEventRegistration>("Closed"); + } + + internal async Task GetCalendarDatePicker() + { + if (m_cp == null) + { + await RunOnUIThread(() => + { + EnsurePickerCreated(); + }); + } + + return m_cp; + } + + private void EnsurePickerCreated() + { + if (m_cp == null) + { + m_cp = new CalendarDatePicker(); + } + + VERIFY_IS_NOT_NULL(m_cp); + } + + internal DependencyObject GetTemplateChild(string childName) + { + EnsurePickerCreated(); + return GetTemplateChild(m_cp, childName); + } + + internal DependencyObject GetTemplateChild(DependencyObject root, string childName) + { + return CalendarHelper.GetTemplateChild(root, childName); + } + + + internal Task PrepareLoadedEvent() + { + return RunOnUIThread(() => + { + EnsurePickerCreated(); + m_loadedRegistration.Attach( + m_cp, + (sender, e) => + { + OnLoaded(); + }); + }); + } + + internal Task PrepareOpenedEvent() + { + return RunOnUIThread(() => + { + EnsurePickerCreated(); + m_openedRegistration.Attach( + m_cp, + ( sender, e) => + { + OnOpened(); + }); + }); + } + + internal Task PrepareClosedEvent() + { + return RunOnUIThread(() => + { + EnsurePickerCreated(); + m_closedRegistration.Attach( + m_cp, + (sender, e) => + { + OnClosed(); + }); + }); + } + + internal Task PrepareSelectedDateChangedEvent() + { + m_addedDates.Clear(); + m_removedDates.Clear(); + + return RunOnUIThread(() => + { + EnsurePickerCreated(); + m_selectedDatesChangedRegistration.Attach( + m_cp, + (sender, e) => + { + OnCalendarViewSelectedDateChanged(sender, e); + }); + }); + } + + internal void ExpectAddedDate(DateTimeOffset date) + { + m_addedDates.Append(date); + } + + internal void ExpectRemovedDate(DateTimeOffset date) + { + m_removedDates.Append(date); + } + + // note: PrepareCICEvent must be called before item get realized + // the best position is before CalendarDatePicker enters visual tree. + internal Task PrepareCICEvent() + { + return RunOnUIThread(() => + { + EnsurePickerCreated(); + m_cicRegistration.Attach( + m_cp, + (sender, e) => + { + OnCalendarViewDayItemChanging(sender as CalendarView, e); + }); + }); + } + + internal async Task WaitForLoaded() + { + await m_loadedEvent.WaitForDefault(); + VERIFY_IS_TRUE(m_loadedEvent.HasFired()); + m_loadedEvent.Reset(); + m_loadedRegistration.Detach(); + } + + internal async Task WaitForOpened() + { + await m_openedEvent.WaitForDefault(); + VERIFY_IS_TRUE(m_openedEvent.HasFired()); + m_openedEvent.Reset(); + m_openedRegistration.Detach(); + } + + internal async Task WaitForClosed() + { + await m_closedEvent.WaitForDefault(); + VERIFY_IS_TRUE(m_closedEvent.HasFired()); + m_closedEvent.Reset(); + m_closedRegistration.Detach(); + } + + internal async Task WaitForSelectedDatesChanged() + { + await m_selectedDatesChangedEvent.WaitForDefault(); + VERIFY_IS_TRUE(m_selectedDatesChangedEvent.HasFired()); + m_selectedDatesChangedEvent.Reset(); + m_selectedDatesChangedRegistration.Detach(); + } + + internal async Task WaitForCICEvent() + { + await m_cicEvent.WaitForDefault(); + VERIFY_IS_TRUE(m_cicEvent.HasFired()); + m_cicEvent.Reset(); + m_cicRegistration.Detach(); + } + + // private + void OnLoaded() + { + LOG_OUTPUT("CalendarDatePickerIntegrationTests: CalendarDatePicker Loaded."); + m_loadedEvent.Set(); + } + + void OnOpened() + { + LOG_OUTPUT("CalendarDatePickerIntegrationTests: CalendarDatePicker CalendarView flyout Opened."); + m_openedEvent.Set(); + } + + void OnClosed() + { + LOG_OUTPUT("CalendarDatePickerIntegrationTests: CalendarDatePicker CalendarView flyout Closed."); + m_closedEvent.Set(); + } + + void OnCalendarViewSelectedDateChanged(object sender, CalendarDatePickerDateChangedEventArgs e) + { + var addedSize = e.NewDate is { } ? 1 : 0; + var removedSize = e.OldDate is { } ? 1 : 0; + VERIFY_ARE_EQUAL(m_addedDates.Size, addedSize); + VERIFY_ARE_EQUAL(m_removedDates.Size, removedSize); + + if (e.NewDate is { }) + { + VERIFY_ARE_EQUAL(e.NewDate.Value.UniversalTime(), m_addedDates.GetAt(0).UniversalTime()); + } + + if (e.OldDate is { }) + { + VERIFY_ARE_EQUAL(e.OldDate.Value.UniversalTime(), m_removedDates.GetAt(0).UniversalTime()); + } + + m_selectedDatesChangedEvent.Set(); + } + + void OnCalendarViewDayItemChanging(CalendarView sender, CalendarViewDayItemChangingEventArgs e) + { + // phase 2: set density bar + // phase 5: blackout + // phase 7: end cic event + + if (e.Phase == 2) + { + CalendarHelper.ColorCollection colors = new CalendarHelper.ColorCollection(); + colors.Append(Colors.Red); + colors.Append(Colors.Green); + colors.Append(Colors.Blue); + colors.Append(Colors.Yellow); + e.Item.SetDensityColors(colors); + } + else if (e.Phase == 5) + { + e.Item.IsBlackout = true; + } + else if (e.Phase == 7) + { + m_cicEvent.Set(); + } + + // keep subscribing cic event until phase 7. + if (e.Phase < 7) + { + e.RegisterUpdateCallback( + (sender, e) => + { + OnCalendarViewDayItemChanging(sender, e); + }); + } + } + + // private + CalendarDatePicker m_cp; + + SafeEventRegistration m_loadedRegistration; + + SafeEventRegistration> m_selectedDatesChangedRegistration; + SafeEventRegistration m_cicRegistration; + SafeEventRegistration> m_openedRegistration; + SafeEventRegistration> m_closedRegistration; + Event m_loadedEvent; + Event m_selectedDatesChangedEvent; + Event m_cicEvent; + Event m_openedEvent; + Event m_closedEvent; + CalendarHelper.DateCollection m_addedDates; + CalendarHelper.DateCollection m_removedDates; + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendardatepicker/CalendarDatePickerIntegrationTests.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendardatepicker/CalendarDatePickerIntegrationTests.cs new file mode 100644 index 000000000000..696ef92c5114 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendardatepicker/CalendarDatePickerIntegrationTests.cs @@ -0,0 +1,955 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System.Linq; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Markup; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Tests.Common; +using FluentAssertions.Execution; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Private.Infrastructure; +using Uno.UI.RuntimeTests.Helpers; +using Uno.UI.RuntimeTests.MUX.Helpers; + +using static Private.Infrastructure.TestServices; +using static Private.Infrastructure.CalendarHelper; + +namespace Windows.UI.Xaml.Tests.Enterprise.CalendarDatePickerTests +{ + [TestClass] + public partial class CalendarDatePickerIntegrationTests : BaseDxamlTestClass + { + [ClassInitialize] + public void ClassSetup() + { + CommonTestSetupHelper.CommonTestClassSetup(); + } + + [TestCleanup] + public void TestCleanup() + { + TestServices.WindowHelper.VerifyTestCleanup(); + } + + // + // Test Cases + // + [TestMethod] + public async Task ValidateDefaultPropertyValues() + { + await RunOnUIThread(() => + { + using var _ = new AssertionScope(); + + var cp = new Windows.UI.Xaml.Controls.CalendarDatePicker(); + VERIFY_ARE_EQUAL(cp.FirstDayOfWeek, Windows.Globalization.DayOfWeek.Sunday); + VERIFY_ARE_EQUAL(cp.DisplayMode, Windows.UI.Xaml.Controls.CalendarViewDisplayMode.Month); + VERIFY_ARE_EQUAL(cp.IsTodayHighlighted, true); + VERIFY_ARE_EQUAL(cp.IsOutOfScopeEnabled, true); + VERIFY_ARE_EQUAL(cp.IsGroupLabelVisible, false); + + Windows.Globalization.Calendar calendar = new Windows.Globalization.Calendar(); + calendar.SetToNow(); + + calendar.AddYears(-100); + + calendar.Month = calendar.FirstMonthInThisYear; + calendar.Day = calendar.FirstDayInThisMonth; + + var minDate = calendar.GetDateTime(); + + calendar.AddYears(200); + + calendar.Month = calendar.LastMonthInThisYear; + calendar.Day = calendar.LastDayInThisMonth; + + var maxDate = calendar.GetDateTime(); + + CompareDate comparer = CalendarHelper.CompareDate; + VERIFY_IS_TRUE(comparer(cp.MinDate, minDate)); + VERIFY_IS_TRUE(comparer(cp.MaxDate, maxDate)); + }); + } + + [TestMethod] + public async Task CanEnterAndLeaveLiveTree() + { + // Note: CalendarDatePicker can't use below commented line to test "CanEnterAndLeaveLiveTree" + // the problem is in below helper, we did these: + // 1. create CalendarDatePicker + // 2. added into visual tree + // 3. test loaded and unloaded event + // 4. remove CalendarDatePicker from visual tree + // 5. destroy CalendarDatePicker + // ..... + + // because we destroy CalendarDatePicker after we remove it from visual tree, so if there are any left work in build tree services, they can't be cleaned up correctly. + // this should happens on ListView and GridView, however for default ListView and GridView (especially in below helper method) are empty and there is no buildtree work. + // But for default CalendarDatePicker, we have! because default CalendarDatePicker will show the dates in 3 years. + + //Generic.FrameworkElementTests.CanEnterAndLeaveLiveTree(); + + TestCleanupWrapper cleanup; + + Grid rootPanel = null; + + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + await helper.PrepareLoadedEvent(); + Windows.UI.Xaml.Controls.CalendarDatePicker cp = await helper.GetCalendarDatePicker(); + + rootPanel = await CreateTestResources(); + + // load into visual tree + await RunOnUIThread(() => + { + rootPanel.Children.Append(cp); + }); + + helper.WaitForLoaded(); + + // remove from visual tree + await RunOnUIThread(() => + { + rootPanel.Children.Clear(); + }); + + await TestServices.WindowHelper.WaitForIdle(); + } + + [TestMethod] + public async Task CanOpenFlyoutByTapping() + { + TestCleanupWrapper cleanup; + + Grid rootPanel = null; + Grid root = null; + TextBlock dateText = null; + FlyoutBase flyout = null; + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + await helper.PrepareLoadedEvent(); + Windows.UI.Xaml.Controls.CalendarDatePicker cp = await helper.GetCalendarDatePicker(); + + rootPanel = await CreateTestResources(); + + // load into visual tree + await RunOnUIThread(() => + { + rootPanel.Children.Append(cp); + }); + + helper.WaitForLoaded(); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + root = Grid(helper.GetTemplateChild("Root")); + dateText = TextBlock(helper.GetTemplateChild("DateText")); + + VERIFY_IS_NOT_NULL(root); + VERIFY_IS_NOT_NULL(dateText); + + flyout = FlyoutBase.GetAttachedFlyout(root); + VERIFY_IS_NOT_NULL(flyout); + }); + + helper.PrepareOpenedEvent(); + + TestServices.InputHelper.Tap(dateText); + + helper.WaitForOpened(); + + await TestServices.WindowHelper.WaitForIdle(); + + helper.PrepareClosedEvent(); + + await RunOnUIThread(() => + { + // close the flyout before exiting. + flyout.Hide(); + }); + helper.WaitForClosed(); + + await TestServices.WindowHelper.WaitForIdle(); + } + + + [TestMethod] + public async Task CanOpenFlyoutByKeyboard() + { + TestCleanupWrapper cleanup; + + Grid rootPanel = null; + + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + await helper.PrepareLoadedEvent(); + Windows.UI.Xaml.Controls.CalendarDatePicker cp = await helper.GetCalendarDatePicker(); + + rootPanel = await CreateTestResources(); + + // load into visual tree + await RunOnUIThread(() => + { + rootPanel.Children.Append(cp); + }); + + helper.WaitForLoaded(); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + cp.Focus(Windows.UI.Xaml.FocusState.Programmatic); + }); + + helper.PrepareOpenedEvent(); + await TestServices.WindowHelper.WaitForIdle(); + + // press enter to open flyout + TestServices.KeyboardHelper.Enter(); + + helper.WaitForOpened(); + + // escape to close the flyout + TestServices.KeyboardHelper.Escape(); + + await TestServices.WindowHelper.WaitForIdle(); + helper.PrepareOpenedEvent(); + + await RunOnUIThread(() => + { + cp.Focus(Windows.UI.Xaml.FocusState.Programmatic); + }); + await TestServices.WindowHelper.WaitForIdle(); + + // press space to open flyout + TestServices.KeyboardHelper.PressKeySequence("$d$_ #$u$_ "); + + helper.WaitForOpened(); + + // escape to close the flyout + TestServices.KeyboardHelper.Escape(); + await TestServices.WindowHelper.WaitForIdle(); + } + + + [TestMethod] + public async Task CanOpenCloseFlyoutBySettingIsCalendarOpen() + { + TestCleanupWrapper cleanup; + + Grid rootPanel = null; + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + await helper.PrepareLoadedEvent(); + Windows.UI.Xaml.Controls.CalendarDatePicker cp = await helper.GetCalendarDatePicker(); + + rootPanel = await CreateTestResources(); + + // load into visual tree + await RunOnUIThread(() => + { + rootPanel.Children.Append(cp); + }); + + helper.WaitForLoaded(); + + await TestServices.WindowHelper.WaitForIdle(); + + helper.PrepareOpenedEvent(); + + await RunOnUIThread(() => + { + cp.IsCalendarOpen = true; + }); + helper.WaitForOpened(); + + helper.PrepareClosedEvent(); + + await RunOnUIThread(() => + { + cp.IsCalendarOpen = false; + }); + + helper.WaitForClosed(); + + await RunOnUIThread(() => + { + // disable CP to make sure input pane is not open during clean up. + cp.IsEnabled = false; + }); + await TestServices.WindowHelper.WaitForIdle(); + } + + [TestMethod] + public async Task CanCloseFlyoutBySelectingADate() + { + TestCleanupWrapper cleanup; + + Grid rootPanel = null; + TextBlock dateText = null; + Grid root = null; + FlyoutBase flyout = null; + CalendarView calendarView = null; + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + await helper.PrepareLoadedEvent(); + Windows.UI.Xaml.Controls.CalendarDatePicker cp = await helper.GetCalendarDatePicker(); + + rootPanel = await CreateTestResources(); + + // load into visual tree + await RunOnUIThread(() => + { + cp.IsOutOfScopeEnabled = false; + rootPanel.Children.Append(cp); + }); + + helper.WaitForLoaded(); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + dateText = TextBlock(helper.GetTemplateChild("DateText")); + VERIFY_IS_NOT_NULL(dateText); + + root = Grid(helper.GetTemplateChild("Root")); + VERIFY_IS_NOT_NULL(root); + + flyout = FlyoutBase.GetAttachedFlyout(root); + VERIFY_IS_NOT_NULL(flyout); + + var content = Flyout(flyout).Content; + calendarView = CalendarView(content); + VERIFY_IS_NOT_NULL(calendarView); + + + calendarView.MinDate = ConvertToDateTime(1, 2000, 1, 1); + calendarView.MaxDate = ConvertToDateTime(1, 2001, 1, 1); + calendarView.UpdateLayout(); + }); + + + TestServices.InputHelper.Tap(dateText); + + await TestServices.WindowHelper.WaitForIdle(); + + helper.PrepareClosedEvent(); + + await RunOnUIThread(() => + { + calendarView.SelectedDates.Append(ConvertToDateTime(1, 2000, 10, 21)); + }); + + helper.WaitForClosed(); + + await RunOnUIThread(() => + { + LOG_OUTPUT("actual text: %s.", dateText.Text); + // Note: below string contains invisible unicode characters (BiDi characters), + // you should always use copy&paste to get the string, directly + // type the string will cause the string comparison fails. + VERIFY_IS_TRUE(dateText.Text == "‎10‎/‎21‎/‎2000"); + }); + + await RunOnUIThread(() => + { + // disable CP to make sure input pane is not open during clean up. + cp.IsEnabled = false; + }); + await TestServices.WindowHelper.WaitForIdle(); + } + + [TestMethod] + public async Task ValidateDateIsCoerced() + { + TestCleanupWrapper cleanup; + + Grid rootPanel = null; + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + await helper.PrepareLoadedEvent(); + Windows.UI.Xaml.Controls.CalendarDatePicker cp = await helper.GetCalendarDatePicker(); + + rootPanel = await CreateTestResources(); + + // load into visual tree + await RunOnUIThread(() => + { + rootPanel.Children.Append(cp); + cp.MinDate = ConvertToDateTime(1, 2000, 1, 1); + cp.MaxDate = ConvertToDateTime(1, 2002, 1, 1); + cp.Date = ConvertToDateTime(1, 2001, 1, 1); + }); + + helper.WaitForLoaded(); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + VERIFY_IS_NOT_NULL(cp.Date); + VERIFY_ARE_EQUAL(cp.Date.Value.UniversalTime(), ConvertToDateTime(1, 2001, 1, 1).UniversalTime()); + + // make date beyond the range. + // it should be coerced to min/max + cp.Date = ConvertToDateTime(1, 2010, 1, 1); + cp.UpdateLayout(); + VERIFY_IS_NOT_NULL(cp.Date); + VERIFY_ARE_EQUAL(cp.Date.Value.UniversalTime(), cp.MaxDate.UniversalTime()); + + cp.Date = ConvertToDateTime(1, 1999, 1, 1); + cp.UpdateLayout(); + VERIFY_IS_NOT_NULL(cp.Date); + VERIFY_ARE_EQUAL(cp.Date.Value.UniversalTime(), cp.MinDate.UniversalTime()); + }); + } + + [TestMethod] + public async Task CanFormatDate() + { + TestCleanupWrapper cleanup; + + Grid rootPanel = null; + TextBlock dateText = null; + + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + await helper.PrepareLoadedEvent(); + Windows.UI.Xaml.Controls.CalendarDatePicker cp = await helper.GetCalendarDatePicker(); + + rootPanel = await CreateTestResources(); + + // load into visual tree + await RunOnUIThread(() => + { + rootPanel.Children.Append(cp); + cp.MinDate = ConvertToDateTime(1, 2000, 1, 1); + cp.MaxDate = ConvertToDateTime(1, 2002, 1, 1); + cp.Date = ConvertToDateTime(1, 2001, 1, 1); + }); + + helper.WaitForLoaded(); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + dateText = TextBlock(helper.GetTemplateChild("DateText")); + VERIFY_IS_NOT_NULL(dateText); + }); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + cp.DateFormat = "{dayofweek.full}, {month.full} {day.integer}, {year.full}"; // equivalent to "longdate" + cp.UpdateLayout(); + + LOG_OUTPUT("actual text: %s.", dateText.Text); + VERIFY_IS_TRUE(dateText.Text == "Monday, January 1, 2001"); + }); + } + + [TestMethod] + public async Task SettingCalendarIdentifierChangesDateFormat() + { + TestCleanupWrapper cleanup; + + Grid rootPanel = null; + TextBlock dateText = null; + + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + await helper.PrepareLoadedEvent(); + Windows.UI.Xaml.Controls.CalendarDatePicker cp = await helper.GetCalendarDatePicker(); + + rootPanel = await CreateTestResources(); + + // load into visual tree + await RunOnUIThread(() => + { + rootPanel.Children.Append(cp); + cp.MinDate = ConvertToDateTime(1, 2000, 1, 1); + cp.MaxDate = ConvertToDateTime(1, 2002, 1, 1); + cp.Date = ConvertToDateTime(1, 2001, 1, 1); + }); + + helper.WaitForLoaded(); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + dateText = TextBlock(helper.GetTemplateChild("DateText")); + VERIFY_IS_NOT_NULL(dateText); + }); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + cp.DateFormat = "{dayofweek.full}, {month.full} {day.integer}, {year.full}"; // equivalent to "longdate" + cp.UpdateLayout(); + + cp.CalendarIdentifier = Windows.Globalization.CalendarIdentifiers.Taiwan; + + LOG_OUTPUT("actual text: %s.", dateText.Text); + VERIFY_IS_TRUE(dateText.Text == "Monday, January 1, 90"); + }); + + } + + [TestMethod] + public async Task PressingDoesNotOpenMenuFlyout() + { + TestCleanupWrapper cleanup; + + Grid rootPanel = null; + Grid root = null; + TextBlock dateText = null; + FlyoutBase flyout = null; + + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + await helper.PrepareLoadedEvent(); + Windows.UI.Xaml.Controls.CalendarDatePicker cp = await helper.GetCalendarDatePicker(); + + var gridPointerPressedEvent = new Event(); + var gridPointerPressedRegistration = CreateSafeEventRegistration("PointerPressed"); + + rootPanel = await CreateTestResources(); + + // load into visual tree + await RunOnUIThread(() => + { + rootPanel.Children.Append(cp); + cp.MinDate = ConvertToDateTime(1, 2000, 1, 1); + cp.MaxDate = ConvertToDateTime(1, 2002, 1, 1); + cp.Date = ConvertToDateTime(1, 2001, 1, 1); + + gridPointerPressedRegistration.Attach(rootPanel, + (object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs args) => + { + gridPointerPressedEvent.Set(); + }); + }); + + helper.WaitForLoaded(); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + root = Grid(helper.GetTemplateChild("Root")); + dateText = TextBlock(helper.GetTemplateChild("DateText")); + + VERIFY_IS_NOT_NULL(root); + VERIFY_IS_NOT_NULL(dateText); + + flyout = FlyoutBase.GetAttachedFlyout(root); + VERIFY_IS_NOT_NULL(flyout); + }); + await TestServices.WindowHelper.WaitForIdle(); + + helper.PrepareOpenedEvent(); + + TestServices.InputHelper.Tap(dateText); + + helper.WaitForOpened(); + + await TestServices.WindowHelper.WaitForIdle(); + helper.PrepareClosedEvent(); + + await RunOnUIThread(() => + { + // close the flyout before exiting. + flyout.Hide(); + }); + helper.WaitForClosed(); + + await TestServices.WindowHelper.WaitForIdle(); + + VERIFY_IS_FALSE(gridPointerPressedEvent.HasFired()); + } + + [TestMethod] + public async Task ValidateUIElementTree() + { + TestCleanupWrapper cleanup; + + StackPanel rootPanel = null; + + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + + await helper.PrepareLoadedEvent(); + Windows.UI.Xaml.Controls.CalendarDatePicker cdp = await helper.GetCalendarDatePicker(); + + await RunOnUIThread(() => + { + rootPanel = StackPanel(XamlReader.Load( + " ") + ); + + Private.Infrastructure.TestServices.WindowHelper.WindowContent = rootPanel; + }); + + // load into visual tree + await RunOnUIThread(() => + { + rootPanel.Children.Append(cdp); + }); + + helper.WaitForLoaded(); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + cdp.Focus(Windows.UI.Xaml.FocusState.Pointer); + }); + + await TestServices.WindowHelper.WaitForIdle(); + + TestServices.Utilities.VerifyUIElementTree(); + } + + [TestMethod] + public async Task ValidateVisualStates() + { + //WUCRenderingScopeGuard guard(DCompRendering.WUCCompleteSynchronousCompTree, false /* resizeWindow */); + + TestServices.WindowHelper.SetWindowSizeOverride(new Size(400, 400)); + + StackPanel rootPanel = null; + + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + await helper.PrepareLoadedEvent(); + CalendarDatePicker cpNormal = await helper.GetCalendarDatePicker(); + CalendarDatePicker cpPressed = null; + CalendarDatePicker cpPointerOver = null; + CalendarDatePicker cpDisabled = null; + CalendarDatePicker cpFocused = null; + CalendarDatePicker cpSelected = null; + + await RunOnUIThread(() => + { + rootPanel = StackPanel(XamlReader.Load( + " ") + ); + + Private.Infrastructure.TestServices.WindowHelper.WindowContent = rootPanel; + }); + + + + // load into visual tree + await RunOnUIThread(() => + { + cpPressed = new Windows.UI.Xaml.Controls.CalendarDatePicker(); + cpPointerOver = new Windows.UI.Xaml.Controls.CalendarDatePicker(); + cpDisabled = new Windows.UI.Xaml.Controls.CalendarDatePicker(); + cpFocused = new Windows.UI.Xaml.Controls.CalendarDatePicker(); + cpSelected = new Windows.UI.Xaml.Controls.CalendarDatePicker(); + + rootPanel.Children.Append(cpNormal); + rootPanel.Children.Append(cpPressed); + rootPanel.Children.Append(cpPointerOver); + rootPanel.Children.Append(cpDisabled); + rootPanel.Children.Append(cpFocused); + rootPanel.Children.Append(cpSelected); + + cpNormal.Header = "Normal"; + cpPressed.Header = "Pressed"; + cpPointerOver.Header = "PointerOver"; + cpDisabled.Header = "Disabled"; + cpFocused.Header = "Focused"; + cpSelected.Header = "Selected"; + }); + + helper.WaitForLoaded(); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + //cpNormal stays in common state + VisualStateManager.GoToState(cpPressed, "Pressed", true); + VisualStateManager.GoToState(cpPointerOver, "PointerOver", true); + VisualStateManager.GoToState(cpDisabled, "Disabled", true); + VisualStateManager.GoToState(cpFocused, "Focused", true); + VisualStateManager.GoToState(cpSelected, "Selected", true); + }); + + await TestServices.WindowHelper.WaitForIdle(); + + TestServices.Utilities.VerifyMockDCompOutput(MockDComp.SurfaceComparison.NoComparison); + } + + [TestMethod] + public async Task DonotResizeCalendarView() + { + TestCleanupWrapper cleanup; + + TestServices.WindowHelper.SetWindowSizeOverride(new Size(400, 400)); + + Grid rootPanel = null; + + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + Windows.UI.Xaml.Controls.CalendarDatePicker cp = await helper.GetCalendarDatePicker(); + + // load into visual tree + await RunOnUIThread(() => + { + rootPanel = Grid(XamlReader.Load( + " ") + ); + rootPanel.Children.Append(cp); + Private.Infrastructure.TestServices.WindowHelper.WindowContent = rootPanel; + cp.IsCalendarOpen = true; + // there is not enough space to show the flyout, before this fix, the flyoutpresenter's content will be clipped + cp.HorizontalAlignment = HorizontalAlignment.Center; + cp.VerticalAlignment = VerticalAlignment.Center; + }); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + var popups = VisualTreeHelper.GetOpenPopupsForXamlRoot(rootPanel.XamlRoot); + VERIFY_ARE_EQUAL(popups.Count, 1); + var popup = popups.GetAt(0); + var presenter = FrameworkElement(popup.Child); + LOG_OUTPUT("actual height: %lf. expected height: 332.", presenter.ActualHeight); + // was 284 before this fix, the calendarview is clipped. + VERIFY_ARE_EQUAL(presenter.ActualHeight, 332); + + cp.IsCalendarOpen = false; + }); + await TestServices.WindowHelper.WaitForIdle(); + } + + [TestMethod] + public async Task CanPresetDate() + { + TestCleanupWrapper cleanup; + + TestServices.WindowHelper.SetWindowSizeOverride(new Size(400, 400)); + + Grid rootPanel = null; + + CalendarDatePickerHelper helper = new CalendarDatePickerHelper(); + Windows.UI.Xaml.Controls.CalendarDatePicker cp = await helper.GetCalendarDatePicker(); + CalendarView calendarView = null; + TextBlock dateText = null; + + rootPanel = await CreateTestResources(); + var date1 = ConvertToDateTime(1, 2000, 10, 21); + var date2 = ConvertToDateTime(1, 2003, 1, 1); + // load into visual tree + await RunOnUIThread(() => + { + rootPanel.Children.Append(cp); + cp.Date = date1; + cp.IsCalendarOpen = true; + }); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + var root = Grid(helper.GetTemplateChild("Root")); + VERIFY_IS_NOT_NULL(root); + + var flyout = FlyoutBase.GetAttachedFlyout(root); + VERIFY_IS_NOT_NULL(flyout); + + var content = Flyout(flyout).Content; + calendarView = CalendarView(content); + VERIFY_IS_NOT_NULL(calendarView); + + dateText = TextBlock(helper.GetTemplateChild("DateText")); + + VERIFY_IS_NOT_NULL(dateText); + }); + + await RunOnUIThread(() => + { + LOG_OUTPUT("actual text: %s.", dateText.Text); + // Note: below string contains invisible unicode characters (BiDi characters), + // you should always use copy&paste to get the string, directly + // type the string will cause the string comparison fails. + VERIFY_IS_TRUE(dateText.Text == "‎10‎/‎21‎/‎2000"); + + VERIFY_ARE_EQUAL(calendarView.SelectedDates.Count, 1); + VERIFY_ARE_EQUAL(calendarView.SelectedDates.GetAt(0).UniversalTime(), date1.UniversalTime()); + }); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + cp.Date = null; + }); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + LOG_OUTPUT("actual text: %s.", dateText.Text); + // clear the Date property will display placehoder text. + VERIFY_IS_TRUE(dateText.Text == cp.PlaceholderText); + + VERIFY_ARE_EQUAL(calendarView.SelectedDates.Count, 0); + }); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + cp.Date = date2; + }); + + await TestServices.WindowHelper.WaitForIdle(); + + await RunOnUIThread(() => + { + LOG_OUTPUT("actual text: %s.", dateText.Text); + // Note: below string contains invisible unicode characters (BiDi characters), + // you should always use copy&paste to get the string, directly + // type the string will cause the string comparison fails. + VERIFY_IS_TRUE(dateText.Text == "‎1‎/‎1‎/‎2003"); + + VERIFY_ARE_EQUAL(calendarView.SelectedDates.Count, 1); + VERIFY_ARE_EQUAL(calendarView.SelectedDates.GetAt(0).UniversalTime(), date2.UniversalTime()); + + cp.IsCalendarOpen = false; + }); + + + await TestServices.WindowHelper.WaitForIdle(); + } + + [TestMethod] + public async Task VerifyTwoWayBinding() + { + TestCleanupWrapper cleanup; + StackPanel rootPanel = null; + Windows.UI.Xaml.Controls.CalendarDatePicker cdp1 = null; + Windows.UI.Xaml.Controls.CalendarDatePicker cdp2 = null; + var date1 = ConvertToDateTime(1, 2000, 1, 1); + var date2 = ConvertToDateTime(1, 2000, 1, 2); + + await RunOnUIThread(() => + { + rootPanel = StackPanel(XamlReader.Load( + " " + + " " + + " " + + "" + )); + + Private.Infrastructure.TestServices.WindowHelper.WindowContent = rootPanel; + }); + + await TestServices.WindowHelper.WaitForIdle(); + + // load into visual tree + await RunOnUIThread(() => + { + cdp1 = CalendarDatePicker(rootPanel.Children.GetAt(0)); + cdp2 = CalendarDatePicker(rootPanel.Children.GetAt(1)); + cdp1.Date = date1; + // due to Bug 5678196:{Binding} doesn't work on nullable properties when source is null + // we can't test the scenario that cdp1.Date is null + CalendarHelper.DumpDate(cdp1.Date.Value, "Changing cdp1.Date to"); + CalendarHelper.DumpDate(cdp2.Date.Value, "Now cdp2.Date is"); + VERIFY_ARE_EQUAL(cdp1.Date.Value.UniversalTime(), cdp2.Date.Value.UniversalTime()); + + cdp2.Date = date2; + CalendarHelper.DumpDate(cdp2.Date.Value, "Changing cdp2.Date to"); + CalendarHelper.DumpDate(cdp1.Date.Value, "Now cdp1.Date is"); + VERIFY_ARE_EQUAL(cdp1.Date.Value.UniversalTime(), cdp2.Date.Value.UniversalTime()); + }); + + } + + [TestMethod] + public async Task TestDateChangedEventWhenAssignDateToSameValue() + { + TestCleanupWrapper cleanup; + Grid rootPanel = null; + Windows.UI.Xaml.Controls.CalendarDatePicker cp = null; + + rootPanel = await CreateTestResources(); + var date = ConvertToDateTime(1, 2000, 1, 1); + + // load into visual tree + await RunOnUIThread(() => + { + cp = new Windows.UI.Xaml.Controls.CalendarDatePicker(); + cp.Date = date; + + rootPanel.Children.Append(cp); + }); + + await TestServices.WindowHelper.WaitForIdle(); + + var dateChangedEvent = new Event(); + var dateChangedRegistration = CreateSafeEventRegistration>("DateChanged"); + + dateChangedRegistration.Attach(cp, + (sender, args) => + { + dateChangedEvent.Set(); + }); + + await RunOnUIThread(() => + { + cp.Date = date; + }); + + await TestServices.WindowHelper.WaitForIdle(); + + // should not raise DateChanged event because we set the same date to cp.Date. + VERIFY_IS_FALSE(dateChangedEvent.HasFired()); + } + + [TestMethod] + public async Task ValidateOverlayBrush() + { + TestCleanupWrapper cleanup; + + CalendarDatePicker calendarDatePicker = null; + + await RunOnUIThread(() => + { + calendarDatePicker = new CalendarDatePicker(); + calendarDatePicker.LightDismissOverlayMode = LightDismissOverlayMode.On; + + TestServices.WindowHelper.WindowContent = calendarDatePicker; + }); + await TestServices.WindowHelper.WaitForIdle(); + + TestServices.InputHelper.Tap(calendarDatePicker); + await TestServices.WindowHelper.WaitForIdle(); + + FlyoutBase flyout = null; + await RunOnUIThread(() => + { + var root = FrameworkElement(VisualTreeHelper.GetChild(calendarDatePicker, 0)); + flyout = FlyoutBase.GetAttachedFlyout(root); + THROW_IF_NULL_WITH_MSG(flyout, "An overlay element should exist for the flyout."); + }); + + FlyoutHelper.ValidateOpenFlyoutOverlayBrush("CalendarDatePickerLightDismissOverlayBackground"); + + FlyoutHelper.HideFlyout(flyout); + } + + } +} diff --git a/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendarview/CalendarViewAutomationPeerIntegrationTests.cs b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendarview/CalendarViewAutomationPeerIntegrationTests.cs new file mode 100644 index 000000000000..6376f678ae38 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/IntegrationTests/dxaml/controls/calendarview/CalendarViewAutomationPeerIntegrationTests.cs @@ -0,0 +1,743 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Windows.UI.Xaml.Automation.Peers; +using Windows.UI.Xaml.Automation.Provider; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Tests.Common; +using Windows.UI.Xaml.Tests.Enterprise; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Private.Infrastructure; +using Uno.UI.RuntimeTests.MUX.Helpers; +using Uno.UI.RuntimeTests.Helpers; + +using static Private.Infrastructure.TestServices; +using static Private.Infrastructure.CalendarHelper; + +namespace Windows.UI.Tests.Enterprise.CalendarViewTests +{ + [TestClass] + public class CalendarViewAutomationPeerIntegrationTests : BaseDxamlTestClass + { + // PeerFromProvider is protected even though conceptually it's a static method. + // BridgeAutomationPeer is a little hack to allow us to call PeerFromProvider in our tests. + private class BridgeAutomationPeer : AutomationPeer + { + // public + internal AutomationPeer CallPeerFromProvider(IRawElementProviderSimple provider) + { + return PeerFromProvider(provider); + } + }; + + [ClassInitialize] + void ClassSetup() + { + CommonTestSetupHelper.CommonTestClassSetup(); + } + + [ClassCleanup] + void TestCleanup() + { + TestServices.WindowHelper.VerifyTestCleanup(); + } + + // + // Test Cases + // + + //[TestMethod] + //public async Task VerifyUIATree() + //{ + // TestCleanupWrapper cleanup; + // Grid rootPanel = null; + + // var helper = new CalendarHelper.CalendarViewHelper(); + // helper.PrepareLoadedEvent(); + // CalendarView cv = await helper.GetCalendarView(); + + // AutomationPropertyChangedHandler spAutomationPropertyChangedEventHandler; + // Automation.AutomationClient.UIAElementInfo uiaInfo; + // uiaInfo.m_Name = "CalendarView"; + // uiaInfo.m_AutomationID = "CalendarView"; + // uiaInfo.m_cType = UIA_CalendarControlTypeId; + + // IUIAutomationElement spUIAutomationElement; + // IUIAutomation spAutomation; + // IUIAutomationElement spAutomationHeaderElement; + // IUIAutomationInvokePattern spInvokePattern; + + // CreateTestResources(rootPanel); + + // await TestServices.RunOnUIThread(() => + // { + // cv.MinDate = ConvertToDateTime(1, 2000, 11, 15); + // cv.MaxDate = ConvertToDateTime(1, 2001, 1, 15); + // AutomationProperties.SetName(cv, uiaInfo.m_Name); + // rootPanel.Children.Add(cv); + // }); + + // helper.WaitForLoaded(); + // await TestServices.WindowHelper.WaitForIdle(); + + // UIAutomationHelper.RunOnCorrectThreadForUIA(() => + // { + // var spAutomationClientManager = + // AutomationClient.AutomationClientManager.CreateAutomationClientManagerFromInfo(uiaInfo); + // spAutomationClientManager.GetCurrentUIAutomationElement(spUIAutomationElement); + // VERIFY_IS_NOT_NULL(spUIAutomationElement); + + // spAutomationClientManager.GetAutomation(spAutomation); + // VERIFY_IS_NOT_NULL(spAutomation); + + // wrl.ComPtr spUIAutomationMonthViewSVCondition; + // Common.AutoVariant autoMonthViewSV; + // autoMonthViewSV.SetString("MonthViewScrollViewer"); + // VERIFY_SUCCEEDED(spAutomation.CreatePropertyCondition(UIA_AutomationIdPropertyId, + // *(autoMonthViewSV.Storage()), spUIAutomationMonthViewSVCondition)); + + // wrl.ComPtr spAutomationMonthViewSVElement; + // VERIFY_SUCCEEDED(spUIAutomationElement.FindFirst(TreeScope.TreeScope_Children, + // spUIAutomationMonthViewSVCondition, spAutomationMonthViewSVElement)); + // VERIFY_IS_NOT_NULL(spAutomationMonthViewSVElement); + + // wrl.ComPtr spUIAutomationDatesCondition; + // Common.AutoVariant autoVarType; + // autoVarType.SetInt(UIA_DataItemControlTypeId); + // VERIFY_SUCCEEDED(spAutomation.CreatePropertyCondition(UIA_ControlTypePropertyId, + // *(autoVarType.Storage()), spUIAutomationDatesCondition)); + // wrl.ComPtr spDateItems; + // VERIFY_SUCCEEDED(spAutomationMonthViewSVElement.FindAll(TreeScope.TreeScope_Children, + // spUIAutomationDatesCondition, spDateItems)); + // int size = 0; + // size = VERIFY_SUCCEEDED(spDateItems.Length); + // WEX.Logging.Log.Comment("Visible Dates are less than or equal to 42"); + // VERIFY_IS_LESS_THAN_OR_EQUAL(size, 42); + + // wrl.ComPtr spUIAutomationAutomationIDCondition; + // Common.AutoVariant autoHeaderButton; + // autoHeaderButton.SetString("HeaderButton"); + // VERIFY_SUCCEEDED(spAutomation.CreatePropertyCondition(UIA_AutomationIdPropertyId, + // *(autoHeaderButton.Storage()), spUIAutomationAutomationIDCondition)); + + // VERIFY_SUCCEEDED(spUIAutomationElement.FindFirst(TreeScope.TreeScope_Children, + // spUIAutomationAutomationIDCondition, spAutomationHeaderElement)); + // VERIFY_IS_NOT_NULL(spAutomationHeaderElement); + + // VERIFY_SUCCEEDED(spAutomationHeaderElement.GetCurrentPatternAs(UIA_InvokePatternId, + // __uuidof(IUIAutomationInvokePattern), spInvokePattern)); + // VERIFY_IS_NOT_NULL(spInvokePattern); + + // VERIFY_SUCCEEDED(spInvokePattern.Invoke()); + // }); + + // await TestServices.WindowHelper.WaitForIdle(); + + // UIAutomationHelper.RunOnCorrectThreadForUIA(() => + // { + // wrl.ComPtr spUIAutomationYearViewSVCondition; + // Common.AutoVariant autoYearViewSV; + // autoYearViewSV.SetString("YearViewScrollViewer"); + // VERIFY_SUCCEEDED(spAutomation.CreatePropertyCondition(UIA_AutomationIdPropertyId, + // *(autoYearViewSV.Storage()), spUIAutomationYearViewSVCondition)); + + // wrl.ComPtr spAutomationYearViewSVElement; + // VERIFY_SUCCEEDED(spUIAutomationElement.FindFirst(TreeScope.TreeScope_Children, + // spUIAutomationYearViewSVCondition, spAutomationYearViewSVElement)); + // VERIFY_IS_NOT_NULL(spAutomationYearViewSVElement); + + // wrl.ComPtr spUIAutomationDatesCondition; + // Common.AutoVariant autoVarType; + // autoVarType.SetInt(UIA_ButtonControlTypeId); + // VERIFY_SUCCEEDED(spAutomation.CreatePropertyCondition(UIA_ControlTypePropertyId, + // *(autoVarType.Storage()), spUIAutomationDatesCondition)); + // wrl.ComPtr spDateItems; + // VERIFY_SUCCEEDED(spAutomationYearViewSVElement.FindAll(TreeScope.TreeScope_Children, + // spUIAutomationDatesCondition, spDateItems)); + // int size = 0; + // size = VERIFY_SUCCEEDED(spDateItems.Length); + // WEX.Logging.Log.Comment("There are 3 month in the view between 2000-11-15 and 2001-1-15"); + // VERIFY_ARE_EQUAL(size, 3); + + // VERIFY_SUCCEEDED(spInvokePattern.Invoke()); + // }); + + // await TestServices.WindowHelper.WaitForIdle(); + + // UIAutomationHelper.RunOnCorrectThreadForUIA(() => + // { + // wrl.ComPtr spUIAutomationDecadeViewSVCondition; + // Common.AutoVariant autoDecadeViewSV; + // autoDecadeViewSV.SetString("DecadeViewScrollViewer"); + // VERIFY_SUCCEEDED(spAutomation.CreatePropertyCondition(UIA_AutomationIdPropertyId, + // *(autoDecadeViewSV.Storage()), spUIAutomationDecadeViewSVCondition)); + + // wrl.ComPtr spAutomationDecadeViewSVElement; + // VERIFY_SUCCEEDED(spUIAutomationElement.FindFirst(TreeScope.TreeScope_Children, + // spUIAutomationDecadeViewSVCondition, spAutomationDecadeViewSVElement)); + // VERIFY_IS_NOT_NULL(spAutomationDecadeViewSVElement); + + // wrl.ComPtr spUIAutomationDatesCondition; + // Common.AutoVariant autoVarType; + // autoVarType.SetInt(UIA_ButtonControlTypeId); + // VERIFY_SUCCEEDED(spAutomation.CreatePropertyCondition(UIA_ControlTypePropertyId, + // *(autoVarType.Storage()), spUIAutomationDatesCondition)); + // wrl.ComPtr spDateItems; + // VERIFY_SUCCEEDED(spAutomationDecadeViewSVElement.FindAll(TreeScope.TreeScope_Children, + // spUIAutomationDatesCondition, spDateItems)); + // int size = 0; + // size = VERIFY_SUCCEEDED(spDateItems.Length); + // WEX.Logging.Log.Comment("There are 2 years in the view between 2000-11-15 and 2001-1-15"); + // VERIFY_ARE_EQUAL(size, 2); + // }); + //} + + //[TestMethod] + //public async Task VerifyElementPatterns() + //{ + // TestCleanupWrapper cleanup; + // Grid rootPanel = null; + + // CalendarHelper.CalendarViewHelper helper = new CalendarHelper.CalendarViewHelper(); + // helper.PrepareLoadedEvent(); + // CalendarView cv = ConfiguredTaskAwaitable<> helper.GetCalendarView(); + + // AutomationClient.AutomationPropertyChangedHandler < + // 3 >> spAutomationPropertyChangedEventHandler; + // Automation.AutomationClient.UIAElementInfo uiaInfo; + // uiaInfo.m_Name = "CalendarView"; + // uiaInfo.m_AutomationID = "CalendarView"; + // uiaInfo.m_cType = UIA_CalendarControlTypeId; + + // wrl.ComPtr spUIAutomationElement; + // wrl.ComPtr spAutomation; + // wrl.ComPtr spAutomationHeaderElement; + // wrl.ComPtr spSelectionItemPattern; + // Windows.Foundation.DateTime testdate = ConvertToDateTime(1, 2000, 11, 15); + // CreateTestResources(rootPanel); + + // await TestServices.RunOnUIThread(() => + // { + // cv.MinDate = ConvertToDateTime(1, 2000, 11, 15); + // cv.MaxDate = ConvertToDateTime(1, 2001, 1, 15); + // AutomationProperties.SetName(cv, uiaInfo.m_Name); + // rootPanel.Children.Add(cv); + // }); + + // helper.WaitForLoaded(); + // await TestServices.WindowHelper.WaitForIdle(); + + // UIAutomationHelper.RunOnCorrectThreadForUIA(() => + // { + // var spAutomationClientManager = + // AutomationClient.AutomationClientManager.CreateAutomationClientManagerFromInfo(uiaInfo); + // spAutomationClientManager.GetCurrentUIAutomationElement(spUIAutomationElement); + // VERIFY_IS_NOT_NULL(spUIAutomationElement); + + // spAutomationClientManager.GetAutomation(spAutomation); + // VERIFY_IS_NOT_NULL(spAutomation); + // }); + + // await TestServices.RunOnUIThread(() => + // { + // cv.SetDisplayDate(ConvertToDateTime(1, 2000, 11, 15)); + // cv.UpdateLayout(); + // }); + + // await TestServices.WindowHelper.WaitForIdle(); + + // UIAutomationHelper.RunOnCorrectThreadForUIA(() => + // { + // wrl.ComPtr spUIAutomationMonthViewSVCondition; + // Common.AutoVariant autoMonthViewSV; + // autoMonthViewSV.SetString("MonthViewScrollViewer"); + // VERIFY_SUCCEEDED(spAutomation.CreatePropertyCondition(UIA_AutomationIdPropertyId, + // *(autoMonthViewSV.Storage()), spUIAutomationMonthViewSVCondition)); + + // wrl.ComPtr spAutomationMonthViewSVElement; + // VERIFY_SUCCEEDED(spUIAutomationElement.FindFirst(TreeScope.TreeScope_Children, + // spUIAutomationMonthViewSVCondition, spAutomationMonthViewSVElement)); + // VERIFY_IS_NOT_NULL(spAutomationMonthViewSVElement); + + // wrl.ComPtr spUIAutomationDatesCondition; + // Common.AutoVariant autoVarType; + // autoVarType.SetInt(UIA_DataItemControlTypeId); + // VERIFY_SUCCEEDED(spAutomation.CreatePropertyCondition(UIA_ControlTypePropertyId, + // *(autoVarType.Storage()), spUIAutomationDatesCondition)); + // wrl.ComPtr spAutomationFirstElement; + // VERIFY_SUCCEEDED(spAutomationMonthViewSVElement.FindFirst(TreeScope.TreeScope_Children, + // spUIAutomationDatesCondition, spAutomationFirstElement)); + + // wrl.ComPtr spGridItemPattern; + // VERIFY_SUCCEEDED(spAutomationFirstElement.GetCurrentPatternAs(UIA_GridItemPatternId, + // __uuidof(IUIAutomationGridItemPattern), spGridItemPattern)); + // VERIFY_IS_NOT_NULL(spGridItemPattern); + + // int col = 0, row = 0; + // col = VERIFY_SUCCEEDED(spGridItemPattern.CurrentColumn); + // WEX.Logging.Log.Comment("2000-11-15 is on Wednesday"); + // VERIFY_ARE_EQUAL(col, 3); + // row = VERIFY_SUCCEEDED(spGridItemPattern.CurrentRow); + // VERIFY_ARE_EQUAL(row, 0); + + // wrl.ComPtr spTableItemPattern; + // VERIFY_SUCCEEDED(spAutomationFirstElement.GetCurrentPatternAs(UIA_TableItemPatternId, + // __uuidof(IUIAutomationTableItemPattern), spTableItemPattern)); + // VERIFY_IS_NOT_NULL(spTableItemPattern); + + // wrl.ComPtr spRowHeaderItems; + // VERIFY_SUCCEEDED(spTableItemPattern.GetCurrentRowHeaderItems(spRowHeaderItems)); + + // int rowSize = 0; + // rowSize = VERIFY_SUCCEEDED(spRowHeaderItems.Length); + // VERIFY_ARE_EQUAL(rowSize, 1); + + // wrl.ComPtr spRowHeaderAutomationElement; + // VERIFY_SUCCEEDED(spRowHeaderItems.GetElement(0, spRowHeaderAutomationElement)); + // VERIFY_IS_NOT_NULL(spRowHeaderAutomationElement); + + // AutoBSTR rowHeaderElementName; + // VERIFY_SUCCEEDED(spRowHeaderAutomationElement.get_CurrentName(rowHeaderElementName)); + // Common.AutoBSTR.VerifyAreEqual("\u200eNovember\u200e \u200e2000", rowHeaderElementName); + + // wrl.ComPtr spHeaderItems; + // VERIFY_SUCCEEDED(spTableItemPattern.GetCurrentColumnHeaderItems(spHeaderItems)); + + // int size = 0; + // size = VERIFY_SUCCEEDED(spHeaderItems.Length); + // VERIFY_ARE_EQUAL(size, 1); + + // wrl.ComPtr spAutomationHeaderElement; + // VERIFY_SUCCEEDED(spHeaderItems.GetElement(0, spAutomationHeaderElement)); + // VERIFY_IS_NOT_NULL(spAutomationHeaderElement); + + // AutoBSTR elementName; + // VERIFY_SUCCEEDED(spAutomationHeaderElement.get_CurrentName(elementName)); + // WEX.Logging.Log.Comment("2000-11-15 is on Wednesday"); + // VERIFY_ARE_EQUAL(wcscmp(elementName, "Wednesday"), 0); + + // VERIFY_SUCCEEDED(spAutomationFirstElement.GetCurrentPatternAs(UIA_SelectionItemPatternId, + // __uuidof(IUIAutomationSelectionItemPattern), spSelectionItemPattern)); + // VERIFY_IS_NOT_NULL(spSelectionItemPattern); + + // BOOL isSelected = true; + // isSelected = VERIFY_SUCCEEDED(spSelectionItemPattern.CurrentIsSelected); + // VERIFY_IS_false(!!isSelected); + // }); + + // helper.PrepareSelectedDatesChangedEvent(); + // helper.ExpectAddedDate(testdate); + // UIAutomationHelper.RunOnCorrectThreadForUIA(() => + // { + // VERIFY_SUCCEEDED(spSelectionItemPattern.Select()); + // }); + // helper.WaitForSelectedDatesChanged(); + + + // helper.PrepareSelectedDatesChangedEvent(); + // helper.ExpectRemovedDate(testdate); + // UIAutomationHelper.RunOnCorrectThreadForUIA(() => + // { + // VERIFY_SUCCEEDED(spSelectionItemPattern.RemoveFromSelection()); + // }); + // helper.WaitForSelectedDatesChanged(); + + // await TestServices.RunOnUIThread(() => + // { + // cv.SelectedDates.Add(testdate); + // }); + + // UIAutomationHelper.RunOnCorrectThreadForUIA(() => + // { + // BOOL isSelected = true; + // isSelected = VERIFY_SUCCEEDED(spSelectionItemPattern.CurrentIsSelected); + // VERIFY_IS_true(!!isSelected); + // }); + //} + + + //[TestMethod] + //public async Task VerifySelectionChangedEvent() + //{ + // TestCleanupWrapper cleanup; + // Grid rootPanel = null; + + // CalendarHelper.CalendarViewHelper helper = new CalendarViewHelper(); + // helper.PrepareLoadedEvent(); + // CalendarView cv = await helper.GetCalendarView(); + + // AutomationClient.AutomationPropertyChangedHandler < + // 3 >> spAutomationPropertyChangedEventHandler; + // Automation.AutomationClient.UIAElementInfo uiaInfo; + // uiaInfo.m_Name = "CalendarView"; + // uiaInfo.m_AutomationID = "CalendarView"; + // uiaInfo.m_cType = UIA_CalendarControlTypeId; + + // wrl.ComPtr spUIAutomationElement; + // wrl.ComPtr spAutomation; + // wrl.ComPtr spAutomationHeaderElement; + // wrl.ComPtr spSelectionItemPattern; + // CreateTestResources(rootPanel); + + // await TestServices.RunOnUIThread(() => + // { + // cv.MinDate = ConvertToDateTime(1, 2000, 11, 15); + // cv.MaxDate = ConvertToDateTime(1, 2001, 1, 15); + // AutomationProperties.SetName(cv, uiaInfo.m_Name); + // rootPanel.Children.Add(cv); + // }); + + // helper.WaitForLoaded(); + // await TestServices.WindowHelper.WaitForIdle(); + + // await TestServices.RunOnUIThread(() => + // { + // cv.SetDisplayDate(ConvertToDateTime(1, 2000, 11, 15)); + // cv.UpdateLayout(); + // }); + + // await TestServices.WindowHelper.WaitForIdle(); + + // UIAutomationHelper.RunOnCorrectThreadForUIA(() => + // { + // var spAutomationClientManager = + // AutomationClient.AutomationClientManager.CreateAutomationClientManagerFromInfo(uiaInfo); + // spAutomationClientManager.GetCurrentUIAutomationElement(spUIAutomationElement); + // VERIFY_IS_NOT_NULL(spUIAutomationElement); + + // spAutomationClientManager.GetAutomation(spAutomation); + // VERIFY_IS_NOT_NULL(spAutomation); + + // wrl.ComPtr spUIAutomationMonthViewSVCondition; + // Common.AutoVariant autoMonthViewSV; + // autoMonthViewSV.SetString("MonthViewScrollViewer"); + // VERIFY_SUCCEEDED(spAutomation.CreatePropertyCondition(UIA_AutomationIdPropertyId, + // *(autoMonthViewSV.Storage()), spUIAutomationMonthViewSVCondition)); + + // wrl.ComPtr spAutomationMonthViewSVElement; + // VERIFY_SUCCEEDED(spUIAutomationElement.FindFirst(TreeScope.TreeScope_Children, + // spUIAutomationMonthViewSVCondition, spAutomationMonthViewSVElement)); + // VERIFY_IS_NOT_NULL(spAutomationMonthViewSVElement); + + // wrl.ComPtr spUIAutomationDatesCondition; + // Common.AutoVariant autoVarType; + // autoVarType.SetInt(UIA_DataItemControlTypeId); + // VERIFY_SUCCEEDED(spAutomation.CreatePropertyCondition(UIA_ControlTypePropertyId, + // *(autoVarType.Storage()), spUIAutomationDatesCondition)); + // wrl.ComPtr spAutomationFirstElement; + // VERIFY_SUCCEEDED(spAutomationMonthViewSVElement.FindFirst(TreeScope.TreeScope_Children, + // spUIAutomationDatesCondition, spAutomationFirstElement)); + + // wrl.ComPtr spSelectionItemPattern; + // VERIFY_SUCCEEDED(spAutomationFirstElement.GetCurrentPatternAs(UIA_SelectionItemPatternId, + // __uuidof(IUIAutomationSelectionItemPattern), spSelectionItemPattern)); + // VERIFY_IS_NOT_NULL(spSelectionItemPattern); + + // await TestServices.RunOnUIThread(() => + // { + // cv.SelectionMode = CalendarViewSelectionMode.Multiple; + // }); + // var spEvent = new Event(); + // wrl.ComPtr spAutomationPatternHandler; + + // spAutomationPatternHandler.Attach(new Patterns.SelectionItemPatternHandler(spAutomationClientManager, + // spEvent, TreeScope_Subtree, UIA_SelectionItem_ElementSelectedEventId)); + // spAutomationPatternHandler.AttachEventHandler(); + // Windows.Foundation.DateTime testdate = ConvertToDateTime(1, 2000, 11, 15); + // await TestServices.RunOnUIThread(() => + // { + // WEX.Logging.Log.Comment("Selecting Date 2000-11-15"); + // cv.SelectedDates.Add(testdate); + // }); + // spAutomationPatternHandler.ConfirmAndUnregister(); + // WEX.Logging.Log.Comment("UIA_SelectionItem_ElementSelectedEventId Recieved"); + + // spAutomationPatternHandler = null; + + // spAutomationPatternHandler.Attach(new Patterns.SelectionItemPatternHandler(spAutomationClientManager, + // spEvent, TreeScope_Subtree, UIA_SelectionItem_ElementAddedToSelectionEventId)); + // spAutomationPatternHandler.AttachEventHandler(); + // await TestServices.RunOnUIThread(() => + // { + // testdate = ConvertToDateTime(1, 2000, 11, 16); + // WEX.Logging.Log.Comment("Selecting Date 2000-11-16"); + // cv.SelectedDates.Add(testdate); + // }); + // spAutomationPatternHandler.ConfirmAndUnregister(); + // WEX.Logging.Log.Comment("UIA_SelectionItem_ElementAddedToSelectionEventId Recieved"); + + // spAutomationPatternHandler = null; + + // spAutomationPatternHandler.Attach(new Patterns.SelectionItemPatternHandler(spAutomationClientManager, + // spEvent, TreeScope_Subtree, UIA_SelectionItem_ElementRemovedFromSelectionEventId)); + // spAutomationPatternHandler.AttachEventHandler(); + // await TestServices.RunOnUIThread(() => + // { + // WEX.Logging.Log.Comment("Removed one selection"); + // cv.SelectedDates.RemoveAt(0); + // }); + // spAutomationPatternHandler.ConfirmAndUnregister(); + // WEX.Logging.Log.Comment("UIA_SelectionItem_ElementRemovedFromSelectionEventId Recieved"); + + + // spAutomationPatternHandler = null; + + // spAutomationPatternHandler.Attach(new Patterns.SelectionItemPatternHandler(spAutomationClientManager, + // spEvent, TreeScope_Subtree, UIA_SelectionItem_ElementRemovedFromSelectionEventId)); + // spAutomationPatternHandler.AttachEventHandler(); + // await TestServices.RunOnUIThread(() => + // { + // WEX.Logging.Log.Comment("Removed last selection"); + // cv.SelectedDates.RemoveAt(0); + // }); + // spAutomationPatternHandler.ConfirmAndUnregister(); + // WEX.Logging.Log.Comment("UIA_SelectionItem_ElementRemovedFromSelectionEventId Recieved"); + // }); + //} + + [TestMethod] + public async Task VerifyDayItemRowHeaders() + { + TestCleanupWrapper cleanup; + CalendarView calendar = default; + + await TestServices.RunOnUIThread(() => + { + calendar = new CalendarView(); + calendar.SetDisplayDate(ConvertToDateTime(1, 2016, 04, 01)); + TestServices.WindowHelper.WindowContent = calendar; + }); + await TestServices.WindowHelper.WaitForIdle(); + + await TestServices.RunOnUIThread(() => + { + var dayItems = new List(); + TreeHelper.GetVisualChildrenByType(calendar, ref dayItems); + + // So since there is no mapping APIs on CalendarView, to get to the month in view, we divide by 2 + // To get to the previous/next months, we remove/add 30 days. + var marchDay = dayItems.GetAt((int)(dayItems.Count * 0.5 - 30)); + var aprilDay = dayItems.GetAt((int)(dayItems.Count * 0.5)); + var mayDay = dayItems.GetAt((int)(dayItems.Count * 0.5 + 30)); + + // We want to validate we get different peers for day items that belong to different months. + var marchDayPeer = FrameworkElementAutomationPeer.CreatePeerForElement(marchDay); + var aprilDayPeer = FrameworkElementAutomationPeer.CreatePeerForElement(aprilDay); + var mayDayPeer = FrameworkElementAutomationPeer.CreatePeerForElement(mayDay); + + var marchRowHeaders = ((ITableItemProvider )marchDayPeer.GetPattern( + PatternInterface.TableItem)).GetRowHeaderItems(); + var aprilRowHeaders = ((ITableItemProvider )aprilDayPeer.GetPattern( + PatternInterface.TableItem)).GetRowHeaderItems(); + var mayRowHeaders = ((ITableItemProvider )mayDayPeer.GetPattern( + PatternInterface.TableItem)).GetRowHeaderItems(); + + VERIFY_ARE_EQUAL(1, marchRowHeaders.Length); + VERIFY_ARE_EQUAL(1, aprilRowHeaders.Length); + VERIFY_ARE_EQUAL(1, mayRowHeaders.Length); + + var bridge = new BridgeAutomationPeer(); + VERIFY_ARE_EQUAL("\u200eMarch\u200e \u200e2016", + bridge.CallPeerFromProvider(marchRowHeaders[0]).GetName()); + VERIFY_ARE_EQUAL("\u200eApril\u200e \u200e2016", + bridge.CallPeerFromProvider(aprilRowHeaders[0]).GetName()); + VERIFY_ARE_EQUAL("\u200eMay\u200e \u200e2016", + bridge.CallPeerFromProvider(mayRowHeaders[0]).GetName()); + }); + } + + //[TestMethod] + //public async Task VerifyOutOfViewItemsDoNotSupportGridItemPattern() + //{ + // TestCleanupWrapper cleanup; + + // Automation.AutomationClient.UIAElementInfo uiaInfo; + // uiaInfo.m_Name = "CalendarView"; + // uiaInfo.m_AutomationID = "CalendarView"; + // uiaInfo.m_cType = UIA_CalendarControlTypeId; + + // CalendarView calendarView = null; + + // await TestServices.RunOnUIThread(() => + // { + // calendarView = new CalendarView(); + // calendarView.SetDisplayDate(ConvertToDateTime(1, 2016, 04, 01)); + + // AutomationProperties.SetName(calendarView, uiaInfo.m_Name); + + // TestServices.WindowHelper.WindowContent = calendarView; + // }); + // await TestServices.WindowHelper.WaitForIdle(); + + // await TestServices.RunOnUIThread(() => + // { + // calendarView.Focus(FocusState.Keyboard); + // }); + // await TestServices.WindowHelper.WaitForIdle(); + + // LOG_OUTPUT("Get the automation element corresponding to the first visible day item."); + // wrl.ComPtr monthScrollViewerChildElement; + // UIAutomationHelper.RunOnCorrectThreadForUIA(() => + // { + // wrl.ComPtr automationElement; + + // var automationClientManager = + // AutomationClient.AutomationClientManager.CreateAutomationClientManagerFromInfo(uiaInfo); + // automationClientManager.GetCurrentUIAutomationElement(automationElement); + + // wrl.ComPtr automation; + // automationClientManager.GetAutomation(automation); + + // wrl.ComPtr monthViewScrollViewerCondition; + // Common.AutoVariant autoMonthViewSV; + // autoMonthViewSV.SetString("MonthViewScrollViewer"); + // LogThrow_IfFailed(automation.CreatePropertyCondition(UIA_AutomationIdPropertyId, + // *(autoMonthViewSV.Storage()), monthViewScrollViewerCondition)); + + // wrl.ComPtr monthViewScrollViewerElement; + // LogThrow_IfFailed(automationElement.FindFirst(TreeScope.TreeScope_Children, + // monthViewScrollViewerCondition, monthViewScrollViewerElement)); + + // wrl.ComPtr trueCondition; + // LogThrow_IfFailed(automation.CreateTrueCondition(trueCondition)); + // LogThrow_IfFailed(monthViewScrollViewerElement.FindFirst(TreeScope.TreeScope_Children, trueCondition, + // &monthScrollViewerChildElement)); + // }); + + // LOG_OUTPUT("Tab past the month header button."); + // TestServices.KeyboardHelper.Tab(); + // await TestServices.WindowHelper.WaitForIdle(); + + // LOG_OUTPUT("Tab past the previous month button."); + // TestServices.KeyboardHelper.Tab(); + // await TestServices.WindowHelper.WaitForIdle(); + + // LOG_OUTPUT("Tab past the next month button."); + // TestServices.KeyboardHelper.Tab(); + // await TestServices.WindowHelper.WaitForIdle(); + + // LOG_OUTPUT( + // "Focus is now on a day item. Press PageDown to change the month and move our previously queried automation element out of view."); + // TestServices.KeyboardHelper.PageDown(); + // await TestServices.WindowHelper.WaitForIdle(); + + // LOG_OUTPUT( + // "Attempt to query for the grid item pattern on our previously queried automation element, which should return null."); + // UIAutomationHelper.RunOnCorrectThreadForUIA(() => + // { + // wrl.ComPtr gridItemPattern; + // LogThrow_IfFailed(monthScrollViewerChildElement.GetCurrentPatternAs(UIA_GridItemPatternId, + // __uuidof(IUIAutomationGridItemPattern), gridItemPattern)); + + // VERIFY_IS_null(gridItemPattern); + // }); + //} + + //[TestMethod] + //public async Task VerifyAutomationNotificationEventAfterClickingNavigationButton() + //{ + // TestCleanupWrapper cleanup; + + // Automation.AutomationClient.UIAElementInfo uiaInfo; + // uiaInfo.m_Name = "CalendarView"; + // uiaInfo.m_AutomationID = "CalendarView"; + // uiaInfo.m_cType = UIA_CalendarControlTypeId; + + // CalendarView calendarView = null; + // Button previousButton = null; + // Button nextButton = null; + + // var loadedEvent = new Event(); + // var previousButtonClickedEvent = new Event(); + // var nextButtonClickedEvent = new Event(); + + // var loadedRegistration = CreateSafeEventRegistration("Loaded"); + // var previousButtonClickRegistration = CreateSafeEventRegistration