File: Terminals\AspireTerminalKeyTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.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.Reflection;
using Hex1b.Input;
 
#pragma warning disable ASPIRETERMINAL001 // Test consumer of the experimental AppHost terminal API.
 
namespace Aspire.Hosting.Tests.Terminals;
 
[Trait("Partition", "2")]
public class AspireTerminalKeyTests
{
    [Fact]
    public void NamedKeys_HaveCompleteUniqueCatalogAndExpectedNativeMappings()
    {
        var namedKeys = GetNamedKeys();
        var properties = typeof(AspireTerminalKey).GetProperties(BindingFlags.Public | BindingFlags.Static);
 
        Assert.Equal(74, namedKeys.Length);
        Assert.Equal(namedKeys.Select(key => key.Name).Order(), properties.Select(property => property.Name).Order());
        Assert.All(properties, property => Assert.Equal(typeof(AspireTerminalKey), property.PropertyType));
        Assert.Equal(namedKeys.Length, namedKeys.Select(key => key.Value).Distinct().Count());
        Assert.Equal(namedKeys.Length, namedKeys.Select(key => key.Value.Key).Distinct().Count());
 
        foreach (var (name, key, nativeKey) in namedKeys)
        {
            Assert.Equal(nativeKey, key.Key);
            Assert.Equal(Hex1bModifiers.None, key.Modifiers);
            Assert.Equal(name, key.ToString());
            Assert.NotEqual(default, key);
            key.Validate("key");
        }
    }
 
    [Fact]
    public void ModifierFactories_PreserveKeyIdentityAndAccumulateModifiers()
    {
        foreach (var (_, key, nativeKey) in GetNamedKeys())
        {
            (AspireTerminalKey Value, Hex1bModifiers Modifiers)[] modifiedKeys =
            [
                (AspireTerminalKey.Ctrl(key), Hex1bModifiers.Control),
                (AspireTerminalKey.Shift(key), Hex1bModifiers.Shift),
                (AspireTerminalKey.Alt(key), Hex1bModifiers.Alt),
                (AspireTerminalKey.Ctrl(AspireTerminalKey.Shift(key)), Hex1bModifiers.Control | Hex1bModifiers.Shift),
                (AspireTerminalKey.Ctrl(AspireTerminalKey.Alt(key)), Hex1bModifiers.Control | Hex1bModifiers.Alt),
                (AspireTerminalKey.Alt(AspireTerminalKey.Shift(key)), Hex1bModifiers.Alt | Hex1bModifiers.Shift),
                (AspireTerminalKey.Ctrl(AspireTerminalKey.Alt(AspireTerminalKey.Shift(key))), Hex1bModifiers.Control | Hex1bModifiers.Alt | Hex1bModifiers.Shift)
            ];
 
            foreach (var (modifiedKey, modifiers) in modifiedKeys)
            {
                Assert.Equal(nativeKey, modifiedKey.Key);
                Assert.Equal(modifiers, modifiedKey.Modifiers);
                modifiedKey.Validate("key");
            }
 
            Assert.Equal(Hex1bModifiers.None, key.Modifiers);
        }
    }
 
    [Fact]
    public void ModifierFactories_RepeatedApplicationIsIdempotent()
    {
        Func<AspireTerminalKey, AspireTerminalKey>[] factories =
        [
            AspireTerminalKey.Ctrl,
            AspireTerminalKey.Shift,
            AspireTerminalKey.Alt
        ];
 
        foreach (var (_, key, _) in GetNamedKeys())
        {
            var allModifiers = AspireTerminalKey.Ctrl(AspireTerminalKey.Alt(AspireTerminalKey.Shift(key)));
 
            foreach (var factory in factories)
            {
                Assert.Equal(factory(key), factory(factory(key)));
                Assert.Equal(allModifiers, factory(allModifiers));
            }
        }
    }
 
    [Fact]
    public void ModifierFactories_AllSixOrdersAreEquivalent()
    {
        foreach (var (_, key, _) in GetNamedKeys())
        {
            AspireTerminalKey[] permutations =
            [
                AspireTerminalKey.Ctrl(AspireTerminalKey.Alt(AspireTerminalKey.Shift(key))),
                AspireTerminalKey.Ctrl(AspireTerminalKey.Shift(AspireTerminalKey.Alt(key))),
                AspireTerminalKey.Alt(AspireTerminalKey.Ctrl(AspireTerminalKey.Shift(key))),
                AspireTerminalKey.Alt(AspireTerminalKey.Shift(AspireTerminalKey.Ctrl(key))),
                AspireTerminalKey.Shift(AspireTerminalKey.Ctrl(AspireTerminalKey.Alt(key))),
                AspireTerminalKey.Shift(AspireTerminalKey.Alt(AspireTerminalKey.Ctrl(key)))
            ];
 
            foreach (var permutation in permutations)
            {
                Assert.Equal(permutations[0], permutation);
                Assert.Equal(permutations[0].GetHashCode(), permutation.GetHashCode());
                Assert.Equal(permutations[0].ToString(), permutation.ToString());
            }
        }
    }
 
    [Fact]
    public void Equality_ComparesKeyIdentityAndModifiers()
    {
        var first = AspireTerminalKey.Ctrl(AspireTerminalKey.Shift(AspireTerminalKey.E));
        var second = AspireTerminalKey.Shift(AspireTerminalKey.Ctrl(AspireTerminalKey.E));
        var differentKey = AspireTerminalKey.Ctrl(AspireTerminalKey.Shift(AspireTerminalKey.F));
        var differentModifiers = AspireTerminalKey.Ctrl(AspireTerminalKey.E);
        IEquatable<AspireTerminalKey> equatable = first;
        object boxed = first;
 
        Assert.True(first.Equals(second));
        Assert.True(equatable.Equals(second));
        Assert.True(boxed.Equals(second));
        Assert.True(first == second);
        Assert.False(first != second);
        Assert.Equal(first.GetHashCode(), second.GetHashCode());
 
        foreach (var different in new[] { differentKey, differentModifiers, default })
        {
            Assert.False(first.Equals(different));
            Assert.False(boxed.Equals(different));
            Assert.False(first == different);
            Assert.True(first != different);
        }
 
        Assert.False(first.Equals(null));
        Assert.False(first.Equals("Ctrl+Shift+E"));
        Assert.False(first.Equals(Hex1bKey.E));
    }
 
    [Fact]
    public void Equality_DefaultValuesAreEqual()
    {
        var first = default(AspireTerminalKey);
        var second = new AspireTerminalKey();
 
        Assert.True(first.Equals(second));
        Assert.True(first.Equals((object)second));
        Assert.True(first == second);
        Assert.False(first != second);
        Assert.Equal(first.GetHashCode(), second.GetHashCode());
    }
 
    [Fact]
    public void ToString_DescribesKeyAndModifiersInCanonicalOrder()
    {
        (AspireTerminalKey Key, string Expected)[] cases =
        [
            (AspireTerminalKey.E, "E"),
            (AspireTerminalKey.Ctrl(AspireTerminalKey.E), "Ctrl+E"),
            (AspireTerminalKey.Alt(AspireTerminalKey.E), "Alt+E"),
            (AspireTerminalKey.Shift(AspireTerminalKey.E), "Shift+E"),
            (AspireTerminalKey.Ctrl(AspireTerminalKey.Alt(AspireTerminalKey.E)), "Ctrl+Alt+E"),
            (AspireTerminalKey.Shift(AspireTerminalKey.Ctrl(AspireTerminalKey.E)), "Ctrl+Shift+E"),
            (AspireTerminalKey.Alt(AspireTerminalKey.Shift(AspireTerminalKey.E)), "Alt+Shift+E"),
            (AspireTerminalKey.Shift(AspireTerminalKey.Alt(AspireTerminalKey.Ctrl(AspireTerminalKey.E))), "Ctrl+Alt+Shift+E"),
            (AspireTerminalKey.Ctrl(AspireTerminalKey.Delete), "Ctrl+Delete"),
            (AspireTerminalKey.Shift(AspireTerminalKey.D1), "Shift+D1"),
            (AspireTerminalKey.Alt(AspireTerminalKey.Backtick), "Alt+Backtick"),
            (default, "Uninitialized")
        ];
 
        foreach (var (key, expected) in cases)
        {
            Assert.Equal(expected, key.ToString());
        }
    }
 
    [Theory]
    [InlineData("key")]
    [InlineData("terminalKey")]
    public void Validate_DefaultValue_ThrowsWithParameterName(string paramName)
    {
        var key = default(AspireTerminalKey);
 
        Assert.Throws<ArgumentException>(paramName, () => key.Validate(paramName));
    }
 
    [Fact]
    public void ModifierFactories_DefaultValue_ThrowsWithKeyParameterName()
    {
        Assert.Throws<ArgumentException>("key", () => AspireTerminalKey.Ctrl(default));
        Assert.Throws<ArgumentException>("key", () => AspireTerminalKey.Shift(default));
        Assert.Throws<ArgumentException>("key", () => AspireTerminalKey.Alt(default));
    }
 
    private static (string Name, AspireTerminalKey Value, Hex1bKey NativeKey)[] GetNamedKeys() =>
    [
        ("A", AspireTerminalKey.A, Hex1bKey.A),
        ("B", AspireTerminalKey.B, Hex1bKey.B),
        ("C", AspireTerminalKey.C, Hex1bKey.C),
        ("D", AspireTerminalKey.D, Hex1bKey.D),
        ("E", AspireTerminalKey.E, Hex1bKey.E),
        ("F", AspireTerminalKey.F, Hex1bKey.F),
        ("G", AspireTerminalKey.G, Hex1bKey.G),
        ("H", AspireTerminalKey.H, Hex1bKey.H),
        ("I", AspireTerminalKey.I, Hex1bKey.I),
        ("J", AspireTerminalKey.J, Hex1bKey.J),
        ("K", AspireTerminalKey.K, Hex1bKey.K),
        ("L", AspireTerminalKey.L, Hex1bKey.L),
        ("M", AspireTerminalKey.M, Hex1bKey.M),
        ("N", AspireTerminalKey.N, Hex1bKey.N),
        ("O", AspireTerminalKey.O, Hex1bKey.O),
        ("P", AspireTerminalKey.P, Hex1bKey.P),
        ("Q", AspireTerminalKey.Q, Hex1bKey.Q),
        ("R", AspireTerminalKey.R, Hex1bKey.R),
        ("S", AspireTerminalKey.S, Hex1bKey.S),
        ("T", AspireTerminalKey.T, Hex1bKey.T),
        ("U", AspireTerminalKey.U, Hex1bKey.U),
        ("V", AspireTerminalKey.V, Hex1bKey.V),
        ("W", AspireTerminalKey.W, Hex1bKey.W),
        ("X", AspireTerminalKey.X, Hex1bKey.X),
        ("Y", AspireTerminalKey.Y, Hex1bKey.Y),
        ("Z", AspireTerminalKey.Z, Hex1bKey.Z),
        ("D0", AspireTerminalKey.D0, Hex1bKey.D0),
        ("D1", AspireTerminalKey.D1, Hex1bKey.D1),
        ("D2", AspireTerminalKey.D2, Hex1bKey.D2),
        ("D3", AspireTerminalKey.D3, Hex1bKey.D3),
        ("D4", AspireTerminalKey.D4, Hex1bKey.D4),
        ("D5", AspireTerminalKey.D5, Hex1bKey.D5),
        ("D6", AspireTerminalKey.D6, Hex1bKey.D6),
        ("D7", AspireTerminalKey.D7, Hex1bKey.D7),
        ("D8", AspireTerminalKey.D8, Hex1bKey.D8),
        ("D9", AspireTerminalKey.D9, Hex1bKey.D9),
        ("F1", AspireTerminalKey.F1, Hex1bKey.F1),
        ("F2", AspireTerminalKey.F2, Hex1bKey.F2),
        ("F3", AspireTerminalKey.F3, Hex1bKey.F3),
        ("F4", AspireTerminalKey.F4, Hex1bKey.F4),
        ("F5", AspireTerminalKey.F5, Hex1bKey.F5),
        ("F6", AspireTerminalKey.F6, Hex1bKey.F6),
        ("F7", AspireTerminalKey.F7, Hex1bKey.F7),
        ("F8", AspireTerminalKey.F8, Hex1bKey.F8),
        ("F9", AspireTerminalKey.F9, Hex1bKey.F9),
        ("F10", AspireTerminalKey.F10, Hex1bKey.F10),
        ("F11", AspireTerminalKey.F11, Hex1bKey.F11),
        ("F12", AspireTerminalKey.F12, Hex1bKey.F12),
        ("Enter", AspireTerminalKey.Enter, Hex1bKey.Enter),
        ("Tab", AspireTerminalKey.Tab, Hex1bKey.Tab),
        ("Escape", AspireTerminalKey.Escape, Hex1bKey.Escape),
        ("Backspace", AspireTerminalKey.Backspace, Hex1bKey.Backspace),
        ("Delete", AspireTerminalKey.Delete, Hex1bKey.Delete),
        ("Insert", AspireTerminalKey.Insert, Hex1bKey.Insert),
        ("Space", AspireTerminalKey.Space, Hex1bKey.Spacebar),
        ("Up", AspireTerminalKey.Up, Hex1bKey.UpArrow),
        ("Down", AspireTerminalKey.Down, Hex1bKey.DownArrow),
        ("Left", AspireTerminalKey.Left, Hex1bKey.LeftArrow),
        ("Right", AspireTerminalKey.Right, Hex1bKey.RightArrow),
        ("Home", AspireTerminalKey.Home, Hex1bKey.Home),
        ("End", AspireTerminalKey.End, Hex1bKey.End),
        ("PageUp", AspireTerminalKey.PageUp, Hex1bKey.PageUp),
        ("PageDown", AspireTerminalKey.PageDown, Hex1bKey.PageDown),
        ("Comma", AspireTerminalKey.Comma, Hex1bKey.OemComma),
        ("Period", AspireTerminalKey.Period, Hex1bKey.OemPeriod),
        ("Minus", AspireTerminalKey.Minus, Hex1bKey.OemMinus),
        ("EqualsSign", AspireTerminalKey.EqualsSign, Hex1bKey.OemPlus),
        ("Slash", AspireTerminalKey.Slash, Hex1bKey.OemQuestion),
        ("Semicolon", AspireTerminalKey.Semicolon, Hex1bKey.Oem1),
        ("LeftBracket", AspireTerminalKey.LeftBracket, Hex1bKey.Oem4),
        ("Backslash", AspireTerminalKey.Backslash, Hex1bKey.Oem5),
        ("RightBracket", AspireTerminalKey.RightBracket, Hex1bKey.Oem6),
        ("Apostrophe", AspireTerminalKey.Apostrophe, Hex1bKey.Oem7),
        ("Backtick", AspireTerminalKey.Backtick, Hex1bKey.OemTilde)
    ];
}