using System;
using System.Collections.Generic;
using NUnit.Framework;
using StardewModdingAPI;
using StardewModdingAPI.Utilities;
namespace SMAPI.Tests.Utilities
{
/// Unit tests for .
[TestFixture]
internal class KeybindListTests
{
/*********
** Unit tests
*********/
/****
** TryParse
****/
/// Assert the parsed fields when constructed from a simple single-key string.
[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.");
}
/// Assert the parsed fields when constructed from multi-key values.
[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();
}
/// Assert invalid values are rejected.
[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
****/
/// Assert that returns the expected result for a given input state.
// 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
*********/
/// Get all defined buttons.
private static IEnumerable GetAllButtons()
{
foreach (SButton button in Enum.GetValues(typeof(SButton)))
yield return button;
}
/// Get the button state defined by a mapping string.
/// The button to check.
/// The state map.
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;
}
}
}