From 928d8289501f3eea30038c6823c648c6e6cb4513 Mon Sep 17 00:00:00 2001 From: h3xds1nz Date: Tue, 21 Jan 2025 08:24:09 +0100 Subject: [PATCH] Add test coverage for public surface of MouseGestureConverter (#10263) --- .../Command/MouseGestureConverter.Tests.cs | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/System/Windows/Input/Command/MouseGestureConverter.Tests.cs diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/System/Windows/Input/Command/MouseGestureConverter.Tests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/System/Windows/Input/Command/MouseGestureConverter.Tests.cs new file mode 100644 index 00000000000..41e82b0953f --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/System/Windows/Input/Command/MouseGestureConverter.Tests.cs @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.Design.Serialization; +using System.ComponentModel; +using System.Globalization; + +namespace System.Windows.Input.Command; + +public sealed class MouseGestureConverterTests +{ + [Theory] + // Valid type + [InlineData(true, typeof(string))] + // Invalid types + [InlineData(false, typeof(MouseAction))] + [InlineData(false, typeof(ModifierKeys))] + [InlineData(false, typeof(KeyGesture))] + [InlineData(false, typeof(MouseGesture))] + [InlineData(false, typeof(InstanceDescriptor))] + public void CanConvertFrom_ReturnsExpected(bool expected, Type sourceType) + { + MouseGestureConverter converter = new(); + + Assert.Equal(expected, converter.CanConvertFrom(sourceType)); + } + + [Theory] + [MemberData(nameof(CanConvertTo_Data))] + public void CanConvertTo_ReturnsExpected(bool expected, bool passContext, object? value, Type? destinationType) + { + MouseGestureConverter converter = new(); + StandardContextImpl context = new() { Instance = value }; + + Assert.Equal(expected, converter.CanConvertTo(passContext ? context : null, destinationType)); + } + + public static IEnumerable CanConvertTo_Data + { + get + { + // Supported cases + yield return new object[] { true, true, new MouseGesture(MouseAction.None, ModifierKeys.Control), typeof(string) }; + yield return new object[] { true, true, new MouseGesture(MouseAction.None, ModifierKeys.Alt), typeof(string) }; + yield return new object[] { true, true, new MouseGesture(MouseAction.MiddleDoubleClick, ModifierKeys.Shift), typeof(string) }; + yield return new object[] { true, true, new MouseGesture(MouseAction.LeftDoubleClick, ModifierKeys.Control | ModifierKeys.Windows | ModifierKeys.Alt), typeof(string) }; + yield return new object[] { true, true, new MouseGesture(MouseAction.WheelClick, ModifierKeys.Control | ModifierKeys.Windows), typeof(string) }; + yield return new object[] { true, true, new MouseGesture(MouseAction.LeftClick, ModifierKeys.Alt | ModifierKeys.Windows), typeof(string) }; + yield return new object[] { true, true, new MouseGesture(MouseAction.RightClick, ModifierKeys.Alt | ModifierKeys.Control), typeof(string) }; + yield return new object[] { true, true, new MouseGesture(MouseAction.RightDoubleClick, ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Control), typeof(string) }; + yield return new object[] { true, true, new MouseGesture(MouseAction.RightDoubleClick, ModifierKeys.None), typeof(string) }; + + // Unsupported cases (Null Context) + yield return new object?[] { false, false, null, typeof(string) }; + // Unsupported cases (Null Context/Destination Type) + yield return new object?[] { false, false, null, null }; + // Unsupported cases (Null Instance) + yield return new object?[] { false, true, null, typeof(string) }; + // Unsupported cases (Null Instance/Destination Type) + yield return new object?[] { false, true, null, null }; + // Unsupported cases (Wrong destination type) + yield return new object?[] { false, true, new MouseGesture(MouseAction.None, ModifierKeys.Control), null }; + yield return new object?[] { false, true, new MouseGesture(MouseAction.WheelClick, ModifierKeys.Control | ModifierKeys.Windows), typeof(KeyGesture) }; + yield return new object?[] { false, true, new MouseGesture(MouseAction.LeftClick, ModifierKeys.Alt | ModifierKeys.Windows), typeof(MouseGesture) }; + yield return new object?[] { false, true, new MouseGesture(MouseAction.None, ModifierKeys.Control), typeof(MouseAction) }; + yield return new object?[] { false, true, new MouseGesture(MouseAction.RightDoubleClick, ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Control), typeof(Key) }; + yield return new object?[] { false, true, new MouseGesture(MouseAction.RightDoubleClick, ModifierKeys.None), typeof(ModifierKeys) }; + // Unsupported cases (Wrong Context Instance) + yield return new object?[] { false, true, new KeyGesture(Key.None, ModifierKeys.Alt), typeof(string) }; + yield return new object?[] { false, true, MouseAction.WheelClick, typeof(string) }; + yield return new object?[] { false, true, Key.F1, typeof(string) }; + + // We do not test for malformed MouseGesture as MouseGesture has to perform its own validation and shall be enforced via its own unit tests + } + } + + [Theory] + [MemberData(nameof(ConvertFrom_ReturnsExpected_Data))] + public void ConvertFrom_ReturnsExpected(MouseGesture expected, ITypeDescriptorContext context, CultureInfo? cultureInfo, string value) + { + MouseGestureConverter converter = new(); + + MouseGesture converted = (MouseGesture)converter.ConvertFrom(context, cultureInfo, value); + Assert.Equal(expected.Modifiers, converted.Modifiers); + Assert.Equal(expected.MouseAction, converted.MouseAction); + } + + public static IEnumerable ConvertFrom_ReturnsExpected_Data + { + get + { + // Supported cases (Culture must stay irrelevant, MouseAction/ModifierKeys also do not care) + yield return new object?[] { new MouseGesture(MouseAction.None, ModifierKeys.None), null, CultureInfo.InvariantCulture, string.Empty }; + yield return new object?[] { new MouseGesture(MouseAction.LeftClick, ModifierKeys.None), null, new CultureInfo("ru-RU"), "LeftClick" }; + yield return new object?[] { new MouseGesture(MouseAction.None, ModifierKeys.Control), null, CultureInfo.InvariantCulture, "Ctrl+" }; + yield return new object?[] { new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control), null, CultureInfo.InvariantCulture, "Ctrl+LeftClick" }; + yield return new object?[] { new MouseGesture(MouseAction.MiddleDoubleClick, ModifierKeys.Alt), null, new CultureInfo("no-NO"), "Alt+MiddleDoubleClick" }; + yield return new object?[] { new MouseGesture(MouseAction.WheelClick, ModifierKeys.Shift), null, CultureInfo.InvariantCulture, "Shift+WheelClick" }; + yield return new object?[] { new MouseGesture(MouseAction.LeftDoubleClick, ModifierKeys.Windows), null, CultureInfo.InvariantCulture, "Windows+LeftDoubleClick" }; + yield return new object?[] { new MouseGesture(MouseAction.RightClick, ModifierKeys.Control | ModifierKeys.Alt), null, CultureInfo.InvariantCulture, "Ctrl+Alt+RightClick" }; + yield return new object?[] { new MouseGesture(MouseAction.RightDoubleClick, ModifierKeys.Control | ModifierKeys.Windows | ModifierKeys.Alt), null, CultureInfo.InvariantCulture, "Ctrl+Alt+Windows+RightDoubleClick" }; + + // Supported cases (fuzzed) + yield return new object?[] { new MouseGesture(MouseAction.None, ModifierKeys.Alt), null, CultureInfo.InvariantCulture, "Alt+ " }; + yield return new object?[] { new MouseGesture(MouseAction.LeftClick, ModifierKeys.None), null, CultureInfo.InvariantCulture, " LeftClick " }; + yield return new object?[] { new MouseGesture(MouseAction.None, ModifierKeys.None), null, CultureInfo.InvariantCulture, " " }; + yield return new object?[] { new MouseGesture(MouseAction.WheelClick, ModifierKeys.Shift), null, CultureInfo.InvariantCulture, "Shift +WheelClick" }; + yield return new object?[] { new MouseGesture(MouseAction.MiddleClick, ModifierKeys.Windows | ModifierKeys.Shift), null, new CultureInfo("no-NO"), " Shift + Windows + MiddleClick " }; + yield return new object?[] { new MouseGesture(MouseAction.LeftDoubleClick, ModifierKeys.Windows), null, CultureInfo.InvariantCulture, "Windows+ LeftDoubleClick " }; + yield return new object?[] { new MouseGesture(MouseAction.RightClick, ModifierKeys.Control | ModifierKeys.Alt), null, CultureInfo.InvariantCulture, "Ctrl+Alt+ RightClick" }; + } + } + + [Theory] + [MemberData(nameof(ConvertFrom_ThrowsNotSupportedException_Data))] + public void ConvertFrom_ThrowsNotSupportedException(CultureInfo? cultureInfo, object value) + { + MouseGestureConverter converter = new(); + + Assert.Throws(() => converter.ConvertFrom(null, cultureInfo, value)); + } + + public static IEnumerable ConvertFrom_ThrowsNotSupportedException_Data + { + get + { + // Nulls are not supported + yield return new object?[] { CultureInfo.InvariantCulture, null }; + // Anything that isn't a string ain't supported + yield return new object?[] { CultureInfo.InvariantCulture, new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control) }; + yield return new object?[] { CultureInfo.InvariantCulture, new KeyGesture(Key.V, ModifierKeys.Control) }; + yield return new object?[] { CultureInfo.InvariantCulture, ModifierKeys.Control }; + yield return new object?[] { CultureInfo.InvariantCulture, MouseAction.LeftDoubleClick }; + yield return new object?[] { CultureInfo.InvariantCulture, Key.V }; + } + } + + [Theory] + [MemberData(nameof(ConvertTo_ReturnsExpected_Data))] + public void ConvertTo_ReturnsExpected(string expected, ITypeDescriptorContext context, CultureInfo? cultureInfo, object? value) + { + MouseGestureConverter converter = new(); + + // Culture and context must not have any meaning + Assert.Equal(expected, converter.ConvertTo(context, cultureInfo, value, typeof(string))); + } + + public static IEnumerable ConvertTo_ReturnsExpected_Data + { + get + { + // Supported null value case that returns string.Empty + yield return new object?[] { string.Empty, null, CultureInfo.InvariantCulture, null }; + + // Supported cases (Culture must stay irrelevant, MouseAction/ModifierKeys also do not care) + yield return new object?[] { string.Empty, null, CultureInfo.InvariantCulture, new MouseGesture(MouseAction.None, ModifierKeys.None) }; + yield return new object?[] { "Alt+", null, CultureInfo.InvariantCulture, new MouseGesture(MouseAction.None, ModifierKeys.Alt) }; + yield return new object?[] { "Windows+", null, new CultureInfo("de-DE"), new MouseGesture(MouseAction.None, ModifierKeys.Windows) }; + yield return new object?[] { "Shift+", null, new CultureInfo("ru-RU"), new MouseGesture(MouseAction.None, ModifierKeys.Shift) }; + yield return new object?[] { "LeftClick", null, CultureInfo.InvariantCulture, new MouseGesture(MouseAction.LeftClick) }; + yield return new object?[] { "Ctrl+LeftClick", null, CultureInfo.InvariantCulture, new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control) }; + yield return new object?[] { "Alt+RightClick", null, CultureInfo.InvariantCulture, new MouseGesture(MouseAction.RightClick, ModifierKeys.Alt) }; + yield return new object?[] { "Windows+WheelClick", null, CultureInfo.InvariantCulture, new MouseGesture(MouseAction.WheelClick, ModifierKeys.Windows) }; + yield return new object?[] { "Alt+RightDoubleClick", null, CultureInfo.InvariantCulture, new MouseGesture(MouseAction.RightDoubleClick, ModifierKeys.Alt) }; + yield return new object?[] { "Ctrl+Alt+Windows+WheelClick", null, new CultureInfo("de-DE"), new MouseGesture(MouseAction.WheelClick, ModifierKeys.Control | ModifierKeys.Windows | ModifierKeys.Alt) }; + yield return new object?[] { "Alt+Windows+MiddleDoubleClick", null, new CultureInfo("ru-RU"), new MouseGesture(MouseAction.MiddleDoubleClick, ModifierKeys.Alt | ModifierKeys.Windows) }; + yield return new object?[] { "Ctrl+Alt+Windows+MiddleClick", null, CultureInfo.InvariantCulture, new MouseGesture(MouseAction.MiddleClick, ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Control) }; + } + } + + [Fact] + public void ConvertTo_ThrowsArgumentNullException() + { + MouseGestureConverter converter = new(); + + Assert.Throws(() => converter.ConvertTo(null, CultureInfo.InvariantCulture, new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control), null)); + } + + [Theory] + [MemberData(nameof(ConvertTo_ThrowsNotSupportedException_Data))] + public void ConvertTo_ThrowsNotSupportedException(object? value, Type? destinationType) + { + MouseGestureConverter converter = new(); + + Assert.Throws(() => converter.ConvertTo(null, CultureInfo.InvariantCulture, value, destinationType)); + } + + public static IEnumerable ConvertTo_ThrowsNotSupportedException_Data + { + get + { + // Wrong destination types + yield return new object?[] { new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control), typeof(MouseGesture) }; + yield return new object?[] { new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control), typeof(KeyGesture) }; + yield return new object?[] { new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control), typeof(MouseAction) }; + yield return new object?[] { new MouseGesture(MouseAction.LeftClick, ModifierKeys.Control), typeof(Key) }; + yield return new object?[] { new MouseGesture(MouseAction.WheelClick, ModifierKeys.Control), typeof(ModifierKeys) }; + // Wrong value types + yield return new object?[] { new KeyGesture(Key.V, ModifierKeys.Control), typeof(string) }; + yield return new object?[] { MouseAction.MiddleDoubleClick, typeof(string) }; + yield return new object?[] { ModifierKeys.Control, typeof(string) }; + yield return new object?[] { Key.V, typeof(string) }; + } + } + + public sealed class StandardContextImpl : ITypeDescriptorContext + { + public IContainer? Container => throw new NotImplementedException(); + + public object? Instance { get; set; } + + public PropertyDescriptor? PropertyDescriptor => throw new NotImplementedException(); + public object? GetService(Type serviceType) => throw new NotImplementedException(); + public void OnComponentChanged() => throw new NotImplementedException(); + public bool OnComponentChanging() => throw new NotImplementedException(); + } +}