Skip to content
Open
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
67 changes: 67 additions & 0 deletions Keybinding.Test/ChordCultureInvarianceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) 2023-2026 ktsu-dev contributors

namespace ktsu.Keybinding.Test;

using System.Globalization;
using ktsu.Keybinding.Core.Models;

[TestClass]
[DoNotParallelize]
public class ChordCultureInvarianceTests
{
// et-EE sorts Z before T, and lt-LT sorts Y before J, so a culture-sensitive sort orders these pairs
// differently from en-US.
[TestMethod]
[DataRow("Ctrl+T+Z", "et-EE")]
[DataRow("Ctrl+J+Y", "lt-LT")]
public void HashCodeAndSetLookupDoNotDependOnTheThreadCulture(string text, string otherCulture)
{
CultureInfo original = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo("en-US");
Chord chord = Chord.Parse(text);
HashSet<Chord> set = [chord];
Dictionary<Chord, string> map = new() { [chord] = "handler" };
int hash = chord.GetHashCode();

CultureInfo.CurrentCulture = new CultureInfo(otherCulture);
Chord reparsed = Chord.Parse(text);

Assert.AreEqual(hash, chord.GetHashCode());
Assert.AreEqual(hash, reparsed.GetHashCode());
Assert.IsTrue(set.Contains(chord));

Check warning on line 33 in Keybinding.Test/ChordCultureInvarianceTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.Contains' instead of 'Assert.IsTrue'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Keybinding&issues=AaGwNwFdkLhceoSA-UF1&open=AaGwNwFdkLhceoSA-UF1&pullRequest=227
Assert.IsTrue(set.Contains(reparsed));

Check warning on line 34 in Keybinding.Test/ChordCultureInvarianceTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.Contains' instead of 'Assert.IsTrue'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Keybinding&issues=AaGwNwFdkLhceoSA-UF2&open=AaGwNwFdkLhceoSA-UF2&pullRequest=227
Assert.IsTrue(map.TryGetValue(reparsed, out string? handler));
Assert.AreEqual("handler", handler);
}
finally
{
CultureInfo.CurrentCulture = original;
}
}

[TestMethod]
[DataRow("Ctrl+T+Z", "et-EE")]
[DataRow("Ctrl+J+Y", "lt-LT")]
public void StoredNoteOrderDoesNotDependOnTheThreadCulture(string text, string otherCulture)
{
CultureInfo original = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo("en-US");
Chord english = Chord.Parse(text);

CultureInfo.CurrentCulture = new CultureInfo(otherCulture);
Chord other = Chord.Parse(text);

CollectionAssert.AreEqual(english.Notes.ToList(), other.Notes.ToList());

Check warning on line 58 in Keybinding.Test/ChordCultureInvarianceTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.AreSequenceEqual' instead of 'CollectionAssert.AreEqual'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_Keybinding&issues=AaGwNwFdkLhceoSA-UF0&open=AaGwNwFdkLhceoSA-UF0&pullRequest=227
Assert.AreEqual(english.ToString(), other.ToString());
Assert.AreEqual(text, other.ToString());
}
finally
{
CultureInfo.CurrentCulture = original;
}
}
}
2 changes: 2 additions & 0 deletions Keybinding.Test/Keybinding.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<IsTestProject>true</IsTestProject>
<TargetFramework>net10.0</TargetFramework>
<TargetFrameworks></TargetFrameworks>
<!-- Culture tests need real culture data; in invariant mode every culture sorts like the invariant one. -->
<InvariantGlobalization>false</InvariantGlobalization>
</PropertyGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions Keybinding.Test/ModifierAliasNormalizationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ private static KeybindingService CreateService()
[DataRow("Windows", "Meta")]
[DataRow("Cmd", "Meta")]
[DataRow("Command", "Meta")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "The test feeds a lowercase alias on purpose.")]
public void EveryConstructionPath_NormalizesModifierAliases(string alias, string canonical)
{
ArgumentNullException.ThrowIfNull(alias);
Expand Down
7 changes: 5 additions & 2 deletions Keybinding/Models/MusicalTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
/// </summary>
/// <param name="key">An uppercase key name</param>
/// <returns>The canonical key name</returns>
private static string CanonicalizeKey(string key)

Check warning on line 72 in Keybinding/Models/MusicalTypes.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

Check warning on line 72 in Keybinding/Models/MusicalTypes.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

Check warning on line 72 in Keybinding/Models/MusicalTypes.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

Check warning on line 72 in Keybinding/Models/MusicalTypes.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

Check warning on line 72 in Keybinding/Models/MusicalTypes.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

Check warning on line 72 in Keybinding/Models/MusicalTypes.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.
{
if (key.Length == 2 && key[0] == 'D' && key[1] is >= '0' and <= '9')
{
Expand Down Expand Up @@ -194,7 +194,9 @@
throw new ArgumentException("Chord cannot contain a null note", nameof(notes));
}

_notes = [.. given.Distinct().OrderBy(n => n.Key.ToString())];
// Ordinal, so the stored order (which ToString, the saved file and GetHashCode rely on) is the same
// whatever the culture of the thread that built the chord.
_notes = [.. given.Distinct().OrderBy(n => n.Key.ToString(), StringComparer.Ordinal)];

if (_notes.Count == 0)
{
Expand Down Expand Up @@ -320,8 +322,9 @@
/// <inheritdoc/>
public override int GetHashCode()
{
// _notes is already in ordinal order, so equal chords hash their notes in the same order on any thread.
HashCode hashCode = new();
foreach (Note note in _notes.OrderBy(n => n.Key.ToString()))
foreach (Note note in _notes)
{
hashCode.Add(note);
}
Expand Down
Loading