Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions Keybinding.Test/ModifierAliasNormalizationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,44 @@
}
}

[TestMethod]
[DataRow("Esc", "Escape")]
[DataRow("Return", "Enter")]
[DataRow("Del", "Delete")]
[DataRow("Ins", "Insert")]
[DataRow("PgUp", "PageUp")]
[DataRow("PgDn", "PageDown")]
[DataRow("Up", "ArrowUp")]
[DataRow("UpArrow", "ArrowUp")]
[DataRow("Down", "ArrowDown")]
[DataRow("DownArrow", "ArrowDown")]
[DataRow("Left", "ArrowLeft")]
[DataRow("LeftArrow", "ArrowLeft")]
[DataRow("Right", "ArrowRight")]
[DataRow("RightArrow", "ArrowRight")]
[DataRow("Option", "Alt")]
[DataRow("Super", "Meta")]
[DataRow("D0", "0")]
[DataRow("D1", "1")]
[DataRow("D2", "2")]
[DataRow("D3", "3")]
[DataRow("D4", "4")]
[DataRow("D5", "5")]
[DataRow("D6", "6")]
[DataRow("D7", "7")]
[DataRow("D8", "8")]
[DataRow("D9", "9")]
public void ChordParse_KeyAliasesMatchCanonicalName(string alias, string canonical)
{
ArgumentNullException.ThrowIfNull(alias);
Chord expected = Chord.Parse($"Ctrl+{canonical}");
Chord actual = Chord.Parse($"Ctrl+{alias}");

Assert.AreEqual(expected, actual);
Assert.AreEqual(expected.GetHashCode(), actual.GetHashCode());
Assert.AreEqual(new Note(canonical), new Note(NoteName.Create(alias.ToUpperInvariant())));
}

[TestMethod]
[DataRow("control")]
[DataRow("Control")]
Expand Down Expand Up @@ -125,4 +163,20 @@
Assert.AreEqual(Chord.Parse("Ctrl+S"), profile.GetChord("save"));
Assert.AreEqual(Chord.Parse("Meta+F"), profile.GetChord("find"));
}

[TestMethod]
public async Task StoredProfileWithKeyAlias_ExecutesCanonicalChordAfterLoad()
{
string json = """
[{"id":"p","name":"Profile","chords":{"save":{"notes":["CTRL","ESC"]}}}]
""";
await File.WriteAllTextAsync(Path.Combine(_testDataDirectory, Constants.Files.ProfilesFileName), json).ConfigureAwait(false);

Check warning on line 173 in Keybinding.Test/ModifierAliasNormalizationTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Keybinding&issues=AaD8u95YDu2wPhADAqyB&open=AaD8u95YDu2wPhADAqyB&pullRequest=164

using KeybindingManager manager = new(_testDataDirectory);
await manager.InitializeAsync().ConfigureAwait(false);
manager.Commands.RegisterCommand(new Command("save", "Save"));
manager.Profiles.SetActiveProfile("p");

Assert.AreEqual("save", manager.Keybindings.ExecuteChord(Chord.Parse("Ctrl+Escape")));
}
}
33 changes: 26 additions & 7 deletions Keybinding/Models/MusicalTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,36 @@ private static string ValidateKey(string key) =>
: key;

/// <summary>
/// Maps a modifier alias to its canonical key name, so that every way of building a note
/// Maps key aliases to canonical key names, so that every way of building a note
/// (parsing, constructing directly, or loading a stored profile) compares and hashes the same.
/// </summary>
/// <param name="key">An uppercase key name</param>
/// <returns>The canonical key name</returns>
private static string CanonicalizeKey(string key) => key switch
private static string CanonicalizeKey(string key)
{
"CONTROL" => "CTRL",
"WIN" or "WINDOWS" or "CMD" or "COMMAND" => "META",
_ => key
};
if (key.Length == 2 && key[0] == 'D' && key[1] is >= '0' and <= '9')
{
return key[1..];
}

return key switch
{
"CONTROL" => "CTRL",
"OPTION" => "ALT",
"WIN" or "WINDOWS" or "CMD" or "COMMAND" or "SUPER" => "META",
"ESC" => nameof(SpecialKeys.Escape).ToUpperInvariant(),
"RETURN" => nameof(SpecialKeys.Enter).ToUpperInvariant(),
"DEL" => nameof(SpecialKeys.Delete).ToUpperInvariant(),
"INS" => nameof(SpecialKeys.Insert).ToUpperInvariant(),
"PGUP" => nameof(SpecialKeys.PageUp).ToUpperInvariant(),
"PGDN" => nameof(SpecialKeys.PageDown).ToUpperInvariant(),
"UP" or "UPARROW" => nameof(SpecialKeys.ArrowUp).ToUpperInvariant(),
"DOWN" or "DOWNARROW" => nameof(SpecialKeys.ArrowDown).ToUpperInvariant(),
"LEFT" or "LEFTARROW" => nameof(SpecialKeys.ArrowLeft).ToUpperInvariant(),
"RIGHT" or "RIGHTARROW" => nameof(SpecialKeys.ArrowRight).ToUpperInvariant(),
_ => key
};
}

/// <summary>
/// Gets the key that this note represents
Expand Down Expand Up @@ -309,7 +328,7 @@ public static Chord Parse(string value)

foreach (string part in parts)
{
// The Note constructor normalizes modifier aliases such as "Control" and "Cmd"
// The Note constructor normalizes key aliases such as "Control" and "Esc"
notes.Add(new Note(part));
}

Expand Down
Loading