summaryrefslogtreecommitdiff
path: root/src/SMAPI.Tests/Utilities/KeybindListTests.cs
blob: c5fd5daf32bb8e29ac3d97989ae500a513f295a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
using System;
using System.Collections.Generic;
using NUnit.Framework;
using StardewModdingAPI;
using StardewModdingAPI.Utilities;

namespace SMAPI.Tests.Utilities
{
    /// <summary>Unit tests for <see cref="KeybindList"/>.</summary>
    [TestFixture]
    internal class KeybindListTests
    {
        /*********
        ** Unit tests
        *********/
        /****
        ** TryParse
        ****/
        /// <summary>Assert the parsed fields when constructed from a simple single-key string.</summary>
        [TestCaseSource(nameof(KeybindListTests.GetAllButtons))]
        public void TryParse_SimpleValue(SButton button)
        {
            // act
            bool success = KeybindList.TryParse($"{button}", out KeybindList? parsed, out string[] errors);

            // assert
            Assert.IsTrue(success, "Parsing unexpectedly failed.");
            Assert.IsNotNull(parsed, "The parsed result should not be null.");
            Assert.AreEqual(parsed!.ToString(), $"{button}");
            Assert.IsNotNull(errors, message: "The errors should never be null.");
            Assert.IsEmpty(errors, message: "The input bindings incorrectly reported errors.");
        }

        /// <summary>Assert the parsed fields when constructed from multi-key values.</summary>
        [TestCase("", ExpectedResult = "None")]
        [TestCase("    ", ExpectedResult = "None")]
        [TestCase(null, ExpectedResult = "None")]
        [TestCase("A + B", ExpectedResult = "A + B")]
        [TestCase("A+B", ExpectedResult = "A + B")]
        [TestCase("      A+     B    ", ExpectedResult = "A + B")]
        [TestCase("a +b", ExpectedResult = "A + B")]
        [TestCase("a +b, LEFTcontrol + leftALT + LeftSHifT + delete", ExpectedResult = "A + B, LeftControl + LeftAlt + LeftShift + Delete")]

        [TestCase(",", ExpectedResult = "None")]
        [TestCase("A,", ExpectedResult = "A")]
        [TestCase(",A", ExpectedResult = "A")]
        public string TryParse_MultiValues(string? input)
        {
            // act
            bool success = KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors);

            // assert
            Assert.IsTrue(success, "Parsing unexpectedly failed.");
            Assert.IsNotNull(parsed, "The parsed result should not be null.");
            Assert.IsNotNull(errors, message: "The errors should never be null.");
            Assert.IsEmpty(errors, message: "The input bindings incorrectly reported errors.");
            return parsed!.ToString();
        }

        /// <summary>Assert invalid values are rejected.</summary>
        [TestCase("+", "Invalid empty button value")]
        [TestCase("A+", "Invalid empty button value")]
        [TestCase("+C", "Invalid empty button value")]
        [TestCase("A + B +, C", "Invalid empty button value")]
        [TestCase("A, TotallyInvalid", "Invalid button value 'TotallyInvalid'")]
        [TestCase("A + TotallyInvalid", "Invalid button value 'TotallyInvalid'")]
        public void TryParse_InvalidValues(string input, string expectedError)
        {
            // act
            bool success = KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors);

            // assert
            Assert.IsFalse(success, "Parsing unexpectedly succeeded.");
            Assert.IsNull(parsed, "The parsed result should be null.");
            Assert.IsNotNull(errors, message: "The errors should never be null.");
            Assert.AreEqual(expectedError, string.Join("; ", errors), "The errors don't match the expected ones.");
        }


        /****
        ** GetState
        ****/
        /// <summary>Assert that <see cref="KeybindList.GetState"/> returns the expected result for a given input state.</summary>
        // single value
        [TestCase("A", "A:Held", ExpectedResult = SButtonState.Held)]
        [TestCase("A", "A:Pressed", ExpectedResult = SButtonState.Pressed)]
        [TestCase("A", "A:Released", ExpectedResult = SButtonState.Released)]
        [TestCase("A", "A:None", ExpectedResult = SButtonState.None)]

        // multiple values
        [TestCase("A + B + C, D", "A:Released, B:None, C:None, D:Pressed", ExpectedResult = SButtonState.Pressed)] // right pressed => pressed
        [TestCase("A + B + C, D", "A:Pressed, B:Held, C:Pressed, D:None", ExpectedResult = SButtonState.Pressed)] // left pressed => pressed
        [TestCase("A + B + C, D", "A:Pressed, B:Pressed, C:Released, D:None", ExpectedResult = SButtonState.None)] // one key released but other keys weren't down last tick => none
        [TestCase("A + B + C, D", "A:Held, B:Held, C:Released, D:None", ExpectedResult = SButtonState.Released)] // all three keys were down last tick and now one is released => released

        // transitive
        [TestCase("A, B", "A: Released, B: Pressed", ExpectedResult = SButtonState.Held)]
        public SButtonState GetState(string input, string stateMap)
        {
            // act
            bool success = KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors);
            if (success && parsed?.Keybinds != null)
            {
                foreach (Keybind? keybind in parsed.Keybinds)
                {
#pragma warning disable 618 // method is marked obsolete because it should only be used in unit tests
                    keybind.GetButtonState = key => this.GetStateFromMap(key, stateMap);
#pragma warning restore 618
                }
            }

            // assert
            Assert.IsTrue(success, "Parsing unexpected failed");
            Assert.IsNotNull(parsed, "The parsed result should not be null.");
            Assert.IsNotNull(errors, message: "The errors should never be null.");
            Assert.IsEmpty(errors, message: "The input bindings incorrectly reported errors.");
            return parsed!.GetState();
        }


        /*********
        ** Private methods
        *********/
        /// <summary>Get all defined buttons.</summary>
        private static IEnumerable<SButton> GetAllButtons()
        {
            foreach (SButton button in Enum.GetValues(typeof(SButton)))
                yield return button;
        }

        /// <summary>Get the button state defined by a mapping string.</summary>
        /// <param name="button">The button to check.</param>
        /// <param name="stateMap">The state map.</param>
        private SButtonState GetStateFromMap(SButton button, string stateMap)
        {
            foreach (string rawPair in stateMap.Split(','))
            {
                // parse values
                string[] parts = rawPair.Split(':', 2, StringSplitOptions.TrimEntries);
                if (!Enum.TryParse(parts[0], ignoreCase: true, out SButton curButton))
                    Assert.Fail($"The state map is invalid: unknown button value '{parts[0]}'");
                if (!Enum.TryParse(parts[1], ignoreCase: true, out SButtonState state))
                    Assert.Fail($"The state map is invalid: unknown state value '{parts[1]}'");

                // get state
                if (curButton == button)
                    return state;
            }

            Assert.Fail($"The state map doesn't define button value '{button}'.");
            return SButtonState.None;
        }
    }
}