What's wrong
The fix for #107 (bbbd73e) aimed to canonicalize modifier aliases "in both Note constructors so every path agrees". The [JsonConstructor] Note(NoteName key) overload (Keybinding/Models/MusicalTypes.cs, around lines 18–22) passes key.ToString() to CanonicalizeKey without Trim().ToUpperInvariant(), while the string overload does both. CanonicalizeKey only matches uppercase text, so:
"control" is not mapped to CTRL;
- a lowercase key such as
"s" is stored as s, not S.
NoteName doesn't enforce uppercase: ValidateNoteName isn't attached as a validation attribute. Chord.ToString() uppercases for display, so the bad chord prints exactly like the correct one. That's the same invisible-mismatch symptom #107 fixed for the other paths. The new tests only feed this constructor NoteName.Create(alias.ToUpperInvariant()), which is why they pass.
Reproduced
[TestMethod]
public void ChordFromNoteNameAlias()
{
Chord built = new([new Note(NoteName.Create("control")), new Note("S")]);
Chord parsed = Chord.Parse("Ctrl+S");
Assert.AreEqual(parsed.ToString(), built.ToString()); // passes: both "Ctrl+S"
Assert.AreEqual(parsed, built); // fails
}
[TestMethod]
public void NoteNameLowercaseAlias() => Assert.AreEqual(new Note("control"), new Note(NoteName.Create("control")));
Observed: ChordFromNoteNameAlias fails with expected: Ctrl+S actual: Ctrl+S. NoteNameLowercaseAlias fails because it gets control where it expects CTRL.
Consequence: a binding built this way never matches, and conflict detection misses it, even though every UI surface shows the same text.
Suggested fix
Route both constructors through one normalizing helper, e.g. CanonicalizeKey(key.ToString().Trim().ToUpperInvariant()), in the NoteName overload too.
Acceptance: both tests above pass, and new Note(NoteName.Create(x)) == new Note(x) for mixed-case aliases and letters.
What's wrong
The fix for #107 (bbbd73e) aimed to canonicalize modifier aliases "in both Note constructors so every path agrees". The
[JsonConstructor] Note(NoteName key)overload (Keybinding/Models/MusicalTypes.cs, around lines 18–22) passeskey.ToString()toCanonicalizeKeywithoutTrim().ToUpperInvariant(), while the string overload does both.CanonicalizeKeyonly matches uppercase text, so:"control"is not mapped toCTRL;"s"is stored ass, notS.NoteNamedoesn't enforce uppercase:ValidateNoteNameisn't attached as a validation attribute.Chord.ToString()uppercases for display, so the bad chord prints exactly like the correct one. That's the same invisible-mismatch symptom #107 fixed for the other paths. The new tests only feed this constructorNoteName.Create(alias.ToUpperInvariant()), which is why they pass.Reproduced
Observed:
ChordFromNoteNameAliasfails withexpected: Ctrl+S actual: Ctrl+S.NoteNameLowercaseAliasfails because it getscontrolwhere it expectsCTRL.Consequence: a binding built this way never matches, and conflict detection misses it, even though every UI surface shows the same text.
Suggested fix
Route both constructors through one normalizing helper, e.g.
CanonicalizeKey(key.ToString().Trim().ToUpperInvariant()), in theNoteNameoverload too.Acceptance: both tests above pass, and
new Note(NoteName.Create(x)) == new Note(x)for mixed-case aliases and letters.