File: System\Windows\Input\KeyConverterTests.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\tests\UnitTests\WindowsBase.Tests\WindowsBase.Tests.csproj (WindowsBase.Tests)
// 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;
using System.ComponentModel.Design.Serialization;
using System.Globalization;
 
namespace System.Windows.Input.Tests;
 
public class KeyConverterTests
{
    public static IEnumerable<object?[]> CanConvertTo_TestData()
    {
        yield return new object?[] { null, null, false };
        yield return new object?[] { null, typeof(object), false };
        yield return new object?[] { null, typeof(string), false };
        yield return new object?[] { null, typeof(InstanceDescriptor), false };
        yield return new object?[] { null, typeof(Key), false };
        yield return new object?[] { new CustomTypeDescriptorContext(), null, false };
        yield return new object?[] { new CustomTypeDescriptorContext(), typeof(object), false };
        yield return new object?[] { new CustomTypeDescriptorContext(), typeof(string), false };
        yield return new object?[] { new CustomTypeDescriptorContext(), typeof(InstanceDescriptor), false };
        yield return new object?[] { new CustomTypeDescriptorContext(), typeof(Key), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, null, false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(object), false };
        // TODO: this should not throw.
        //yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(string), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(InstanceDescriptor), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(Key), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None }, null, false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None }, typeof(object), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None }, typeof(string), true };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None }, typeof(InstanceDescriptor), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None }, typeof(Key), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.Cancel }, null, false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.Cancel }, typeof(object), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.Cancel }, typeof(string), true };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.Cancel }, typeof(InstanceDescriptor), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.Cancel }, typeof(Key), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.A }, null, false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.A }, typeof(object), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.A }, typeof(string), true };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.A }, typeof(InstanceDescriptor), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.A }, typeof(Key), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.OemClear }, null, false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.OemClear }, typeof(object), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.OemClear }, typeof(string), true };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.OemClear }, typeof(InstanceDescriptor), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.OemClear }, typeof(Key), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed }, null, false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed }, typeof(object), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed }, typeof(string), true };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed }, typeof(InstanceDescriptor), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed }, typeof(Key), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None - 1 }, null, false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None - 1 }, typeof(object), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None - 1 }, typeof(string), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None - 1 }, typeof(InstanceDescriptor), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None - 1 }, typeof(Key), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed + 1 }, null, false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed + 1 }, typeof(object), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed + 1 }, typeof(string), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed + 1 }, typeof(InstanceDescriptor), false };
        yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed + 1 }, typeof(Key), false };
    }
 
    [Theory]
    [MemberData(nameof(CanConvertTo_TestData))]
    public void CanConvertTo_Invoke_ReturnsExpected(ITypeDescriptorContext context, Type destinationType, bool expected)
    {
        var converter = new KeyConverter();
        Assert.Equal(expected, converter.CanConvertTo(context, destinationType));
    }
 
    [Fact]
    public void CanConvertTo_InvokeToStringInstanceNotKey_ThrowsInvalidCastException()
    {
        // TODO: this should return false.
        var converter = new KeyConverter();
        var context = new CustomTypeDescriptorContext { Instance = new object() };
        Assert.Throws<InvalidCastException>(() => converter.CanConvertTo(context, typeof(string)));
    }
 
    public static IEnumerable<object[]> ConvertTo_KeyToString_TestData()
    {
        yield return new object[] { Key.None, "" };
        yield return new object[] { Key.Cancel, "Cancel" };
        yield return new object[] { Key.Back, "Backspace" };
        yield return new object[] { Key.Tab, "Tab" };
        yield return new object[] { Key.LineFeed, "Clear" };
        yield return new object[] { Key.Return, "Return" };
        yield return new object[] { Key.HanjaMode, "HanjaMode" };
        yield return new object[] { Key.Escape, "Esc" };
        yield return new object[] { Key.ImeConvert, "ImeConvert" };
        yield return new object[] { Key.Help, "Help" };
        yield return new object[] { Key.D0, "0" };
        yield return new object[] { Key.D1, "1" };
        yield return new object[] { Key.D2, "2" };
        yield return new object[] { Key.D3, "3" };
        yield return new object[] { Key.D4, "4" };
        yield return new object[] { Key.D5, "5" };
        yield return new object[] { Key.D6, "6" };
        yield return new object[] { Key.D7, "7" };
        yield return new object[] { Key.D8, "8" };
        yield return new object[] { Key.D9, "9" };
        yield return new object[] { Key.A, "A" };
        yield return new object[] { Key.B, "B" };
        yield return new object[] { Key.C, "C" };
        yield return new object[] { Key.D, "D" };
        yield return new object[] { Key.E, "E" };
        yield return new object[] { Key.F, "F" };
        yield return new object[] { Key.G, "G" };
        yield return new object[] { Key.H, "H" };
        yield return new object[] { Key.I, "I" };
        yield return new object[] { Key.J, "J" };
        yield return new object[] { Key.K, "K" };
        yield return new object[] { Key.L, "L" };
        yield return new object[] { Key.M, "M" };
        yield return new object[] { Key.N, "N" };
        yield return new object[] { Key.O, "O" };
        yield return new object[] { Key.P, "P" };
        yield return new object[] { Key.Q, "Q" };
        yield return new object[] { Key.R, "R" };
        yield return new object[] { Key.S, "S" };
        yield return new object[] { Key.T, "T" };
        yield return new object[] { Key.U, "U" };
        yield return new object[] { Key.V, "V" };
        yield return new object[] { Key.W, "W" };
        yield return new object[] { Key.X, "X" };
        yield return new object[] { Key.Y, "Y" };
        yield return new object[] { Key.Z, "Z" };
        yield return new object[] { Key.LWin, "LWin" };
        yield return new object[] { Key.DeadCharProcessed, "DeadCharProcessed" };
    }
 
    [Theory]
    [MemberData(nameof(ConvertTo_KeyToString_TestData))]
    public void ConvertTo_InvokeKeyToString_ReturnsExpected(Key value, string expected)
    {
        var converter = new KeyConverter();
        Assert.Equal(expected, converter.ConvertTo(value, typeof(string)));
        Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string)));
        Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string)));
    }
    
    [Theory]
    [InlineData((Key)int.MinValue)]
    [InlineData((Key)(-1))]
    [InlineData(Key.DeadCharProcessed + 1)]
    [InlineData((Key)int.MaxValue)]
    public void ConvertTo_InvalidKey_ThrowsNotSupportedException(Key value)
    {
        var converter = new KeyConverter();
        Assert.Throws<NotSupportedException>(() => converter.ConvertTo(value, typeof(string)));
        Assert.Throws<NotSupportedException>(() => converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string)));
        Assert.Throws<NotSupportedException>(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string)));
    }
 
    [Theory]
    [InlineData(null)]
    // TODO: this should not throw InvalidCastException.
    //[InlineData("", "")]
    //[InlineData("value", "value")]
    public void ConvertTo_InvokeNotKeyToStringNull_ThrowsNotSupportedException(object? value)
    {
        var converter = new KeyConverter();
        Assert.Throws<NotSupportedException>(() => converter.ConvertTo(value, typeof(string)));
        Assert.Throws<NotSupportedException>(() => converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string)));
        Assert.Throws<NotSupportedException>(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string)));
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("value")]
    public void ConvertTo_InvokeNotKeyToStringNotNull_ThrowsInvalidCastException(object value)
    {
        // TODO: this should not throw InvalidCastException.
        var converter = new KeyConverter();
        Assert.Throws<InvalidCastException>(() => converter.ConvertTo(value, typeof(string)));
        Assert.Throws<InvalidCastException>(() => converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string)));
        Assert.Throws<InvalidCastException>(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string)));
    }
 
    public static IEnumerable<object?[]> ConvertTo_CantConvert_TestData()
    {
        yield return new object?[] { null, typeof(object) };
        yield return new object?[] { string.Empty, typeof(object) };
        yield return new object?[] { "value", typeof(object) };
        yield return new object?[] { new object(), typeof(object) };
        yield return new object?[] { Key.None, typeof(object) };
        
        yield return new object?[] { null, typeof(Key) };
        yield return new object?[] { string.Empty, typeof(Key) };
        yield return new object?[] { "value", typeof(Key) };
        yield return new object?[] { new object(), typeof(Key) };
        yield return new object?[] { Key.None, typeof(Key) };
    }
 
    [Theory]
    [MemberData(nameof(ConvertTo_CantConvert_TestData))]
    public void ConvertTo_CantConvert_ThrowsNotSupportedException(object value, Type destinationType)
    {
        var converter = new KeyConverter();
        Assert.Throws<NotSupportedException>(() => converter.ConvertTo(value, destinationType));
        Assert.Throws<NotSupportedException>(() => converter.ConvertTo(null, null, value, destinationType));
        Assert.Throws<NotSupportedException>(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, destinationType));
    }
 
    public static IEnumerable<object?[]> ConvertTo_NullDestinationType_TestData()
    {
        yield return new object?[] { null };
        yield return new object?[] { string.Empty };
        yield return new object?[] { "value" };
        yield return new object?[] { new object() };
        yield return new object?[] { Key.None };
    }
 
    [Theory]
    [MemberData(nameof(ConvertTo_NullDestinationType_TestData))]
    public void ConvertTo_NullDestinationType_ThrowsArgumentNullException(object value)
    {
        var converter = new KeyConverter();
        Assert.Throws<ArgumentNullException>("destinationType", () => converter.ConvertTo(value, null!));
        Assert.Throws<ArgumentNullException>("destinationType", () => converter.ConvertTo(null, null, Key.None, null!));
        Assert.Throws<ArgumentNullException>("destinationType", () => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, Key.None, null!));
    }
 
    [Theory]
    [InlineData(null, false)]
    [InlineData(typeof(object), false)]
    [InlineData(typeof(string), true)]
    [InlineData(typeof(InstanceDescriptor), false)]
    [InlineData(typeof(Key), false)]
    public void CanConvertFrom_Invoke_ReturnsExpected(Type? sourceType, bool expected)
    {
        var converter = new KeyConverter();
        Assert.Equal(expected, converter.CanConvertFrom(sourceType!));
        Assert.Equal(expected, converter.CanConvertFrom(null, sourceType));
        Assert.Equal(expected, converter.CanConvertFrom(new CustomTypeDescriptorContext(), sourceType));
    }
 
    public static IEnumerable<object[]> ConvertFrom_TestData()
    {
        yield return new object[] { "", Key.None };
        yield return new object[] { "  ", Key.None };
        yield return new object[] { "0", Key.D0 };
        yield return new object[] { " 0 ", Key.D0 };
        yield return new object[] { "1", Key.D1 };
        yield return new object[] { "2", Key.D2 };
        yield return new object[] { "3", Key.D3 };
        yield return new object[] { "4", Key.D4 };
        yield return new object[] { "5", Key.D5 };
        yield return new object[] { "6", Key.D6 };
        yield return new object[] { "7", Key.D7 };
        yield return new object[] { "8", Key.D8 };
        yield return new object[] { "9", Key.D9 };
        yield return new object[] { "A", Key.A };
        yield return new object[] { "B", Key.B };
        yield return new object[] { "C", Key.C };
        yield return new object[] { "D", Key.D };
        yield return new object[] { "E", Key.E };
        yield return new object[] { "F", Key.F };
        yield return new object[] { "G", Key.G };
        yield return new object[] { "H", Key.H };
        yield return new object[] { "I", Key.I };
        yield return new object[] { "J", Key.J };
        yield return new object[] { "K", Key.K };
        yield return new object[] { "L", Key.L };
        yield return new object[] { "M", Key.M };
        yield return new object[] { "N", Key.N };
        yield return new object[] { "O", Key.O };
        yield return new object[] { "P", Key.P };
        yield return new object[] { "Q", Key.Q };
        yield return new object[] { "R", Key.R };
        yield return new object[] { "S", Key.S };
        yield return new object[] { "T", Key.T };
        yield return new object[] { "U", Key.U };
        yield return new object[] { "V", Key.V };
        yield return new object[] { "W", Key.W };
        yield return new object[] { "X", Key.X };
        yield return new object[] { "Y", Key.Y };
        yield return new object[] { "Z", Key.Z };
        yield return new object[] { "a", Key.A };
        yield return new object[] { "b", Key.B };
        yield return new object[] { "c", Key.C };
        yield return new object[] { "d", Key.D };
        yield return new object[] { "e", Key.E };
        yield return new object[] { "f", Key.F };
        yield return new object[] { "g", Key.G };
        yield return new object[] { "h", Key.H };
        yield return new object[] { "i", Key.I };
        yield return new object[] { "j", Key.J };
        yield return new object[] { "k", Key.K };
        yield return new object[] { "l", Key.L };
        yield return new object[] { "m", Key.M };
        yield return new object[] { "n", Key.N };
        yield return new object[] { "o", Key.O };
        yield return new object[] { "p", Key.P };
        yield return new object[] { "q", Key.Q };
        yield return new object[] { "r", Key.R };
        yield return new object[] { "s", Key.S };
        yield return new object[] { "t", Key.T };
        yield return new object[] { "u", Key.U };
        yield return new object[] { "v", Key.V };
        yield return new object[] { "w", Key.W };
        yield return new object[] { "x", Key.X };
        yield return new object[] { "y", Key.Y };
        yield return new object[] { "z", Key.Z };
        yield return new object[] { "ENTER", Key.Return };
        yield return new object[] { " ENTER ", Key.Return };
        yield return new object[] { " eNtEr ", Key.Return };
        yield return new object[] { "ESC", Key.Escape };
        yield return new object[] { "PGUP", Key.PageUp };
        yield return new object[] { "PGDN", Key.PageDown };
        yield return new object[] { "PRTSC", Key.PrintScreen };
        yield return new object[] { "INS", Key.Insert };
        yield return new object[] { "DEL", Key.Delete };
        yield return new object[] { "WINDOWS", Key.LWin };
        yield return new object[] { "WIN", Key.LWin };
        yield return new object[] { "LEFTWINDOWS", Key.LWin };
        yield return new object[] { "RIGHTWINDOWS", Key.RWin };
        yield return new object[] { "APPS", Key.Apps };
        yield return new object[] { "APPLICATION", Key.Apps };
        yield return new object[] { "BREAK", Key.Cancel };
        yield return new object[] { "BACKSPACE", Key.Back };
        yield return new object[] { "BKSP", Key.Back };
        yield return new object[] { "BS", Key.Back };
        yield return new object[] { "SHIFT", Key.LeftShift };
        yield return new object[] { "LEFTSHIFT", Key.LeftShift };
        yield return new object[] { "RIGHTSHIFT", Key.RightShift };
        yield return new object[] { "CONTROL", Key.LeftCtrl };
        yield return new object[] { "CTRL", Key.LeftCtrl };
        yield return new object[] { "LEFTCTRL", Key.LeftCtrl };
        yield return new object[] { "RIGHTCTRL", Key.RightCtrl };
        yield return new object[] { "ALT", Key.LeftAlt };
        yield return new object[] { "LEFTALT", Key.LeftAlt };
        yield return new object[] { "RIGHTALT", Key.RightAlt };
        yield return new object[] { "SEMICOLON", Key.OemSemicolon };
        yield return new object[] { "PLUS", Key.OemPlus };
        yield return new object[] { "COMMA", Key.OemComma };
        yield return new object[] { "MINUS", Key.OemMinus };
        yield return new object[] { "PERIOD", Key.OemPeriod };
        yield return new object[] { "QUESTION", Key.OemQuestion };
        yield return new object[] { "TILDE", Key.OemTilde };
        yield return new object[] { "OPENBRACKETS", Key.OemOpenBrackets };
        yield return new object[] { "PIPE", Key.OemPipe };
        yield return new object[] { "CLOSEBRACKETS", Key.OemCloseBrackets };
        yield return new object[] { "QUOTES", Key.OemQuotes };
        yield return new object[] { "BACKSLASH", Key.OemBackslash };
        yield return new object[] { "FINISH", Key.OemFinish };
        yield return new object[] { "ATTN", Key.Attn };
        yield return new object[] { "CRSEL", Key.CrSel };
        yield return new object[] { "EXSEL", Key.ExSel };
        yield return new object[] { "ERASEEOF", Key.EraseEof };
        yield return new object[] { "PLAY", Key.Play };
        yield return new object[] { "ZOOM", Key.Zoom };
        yield return new object[] { "PA1", Key.Pa1 };
        
        yield return new object[] { "Cancel", Key.Cancel };
        yield return new object[] { "CANCEL", Key.Cancel };
        yield return new object[] { " CANCEL ", Key.Cancel };
    }
 
    [Theory]
    [MemberData(nameof(ConvertFrom_TestData))]
    public void ConvertFrom_InvokeStringValue_ReturnsExpected(string value, Key expected)
    {
        var converter = new KeyConverter();
        Assert.Equal(expected, converter.ConvertFrom(value));
        Assert.Equal(expected, converter.ConvertFrom(null, null, value));
        Assert.Equal(expected, converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value));
    }
 
    [Fact]
    public void ConvertFrom_NullValue_ThrowsNotSupportedException()
    {
        var converter = new KeyConverter();
        Assert.Throws<NotSupportedException>(() => converter.ConvertFrom(null!));
        Assert.Throws<NotSupportedException>(() => converter.ConvertFrom(null, null, null));
        Assert.Throws<NotSupportedException>(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, null));
    }
 
    public static IEnumerable<object[]> ConvertFrom_InvalidValue_TestData()
    {
        yield return new object[] { "_" };
        yield return new object[] { " _ " };
        yield return new object[] { "\u0663" };
        yield return new object[] { " \u0663 " };
        yield return new object[] { "\u0409" };
        yield return new object[] { " \u0409 " };
        yield return new object[] { "NOSUCHKEY" };
        yield return new object[] { " NOSUCHKEY " };
    }
 
    [Theory]
    [MemberData(nameof(ConvertFrom_InvalidValue_TestData))]
    public void ConvertFrom_InvokeInvalidValue_ThrowsArgumentException(string value)
    {
        var converter = new KeyConverter();
        // TODO: add paramName.
        Assert.Throws<ArgumentException>(() => converter.ConvertFrom(value));
        Assert.Throws<ArgumentException>(() => converter.ConvertFrom(null, null, value));
        Assert.Throws<ArgumentException>(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value));
    }
    
    public static IEnumerable<object[]> ConvertFrom_CantConvert_TestData()
    {
        yield return new object[] { new object() };
        yield return new object[] { Key.A };
        yield return new object[] { ModifierKeys.None };
    }
    
    [Theory]
    [MemberData(nameof(ConvertFrom_CantConvert_TestData))]
    public void ConvertFrom_CantConvert_ThrowsNotSupportedException(object value)
    {
        var converter = new KeyConverter();
        Assert.Throws<NotSupportedException>(() => converter.ConvertFrom(value));
        Assert.Throws<NotSupportedException>(() => converter.ConvertFrom(null, null, value));
        Assert.Throws<NotSupportedException>(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value));
    }
 
    private class CustomTypeDescriptorContext : ITypeDescriptorContext
    {
        public IContainer Container => throw new NotImplementedException();
 
        private object? _instance;
 
        public object Instance
        {
            get => _instance!;
            set => _instance = value;
        }
 
        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();
    }
}