Unit tests for CharacterNameSkeleton in the application layer. Verifies that visually confusable names collapse to the same skeleton, that whole-name homoglyphs in other scripts map to Latin, that Unicode-aware folding rules are followed, and that distinct names remain distinct or produce empty skeletons when no identity-bearing characters exist.
using Hexagon.V2.Application;
namespace Hexagon.V2.Tests.Application;
[TestClass]
public sealed class CharacterNameSkeletonTests
{
[TestMethod]
public void WithinScriptLookAlikesCollapseOntoTheNameTheyImitate()
{
var alice = CharacterNameSkeleton.Of( "Alice" );
// Digit-for-letter and the i/l/1 shape collision.
Assert.AreEqual( alice, CharacterNameSkeleton.Of( "A1ice" ) );
Assert.AreEqual( alice, CharacterNameSkeleton.Of( "Alice" ) );
Assert.AreEqual( alice, CharacterNameSkeleton.Of( "AIice" ) );
Assert.AreEqual( alice, CharacterNameSkeleton.Of( "4lice" ) );
// Separators and punctuation carry no identity.
Assert.AreEqual( alice, CharacterNameSkeleton.Of( "A l i c e" ) );
Assert.AreEqual( alice, CharacterNameSkeleton.Of( "A-l-i-c-e" ) );
// Accents are decoration, not identity.
Assert.AreEqual( CharacterNameSkeleton.Of( "Jose" ), CharacterNameSkeleton.Of( "Jos\u00E9" ) );
// The classic digraph pair.
Assert.AreEqual( CharacterNameSkeleton.Of( "Bemard" ), CharacterNameSkeleton.Of( "Bernard" ) );
Assert.AreEqual( CharacterNameSkeleton.Of( "Walker" ), CharacterNameSkeleton.Of( "VValker" ) );
}
[TestMethod]
public void WholeNameWrittenInAnotherScriptCollapsesOntoTheLatinNameItImitates()
{
// The script profile permits these - they are single-script names. Only the skeleton
// sees that they are drawn to read as "Alice" and "Petro".
// Cyrillic es-o-o-er-ie-ghe, which draws "Cooper" without one Latin letter in it.
Assert.AreEqual(
CharacterNameSkeleton.Of( "Cooper" ),
CharacterNameSkeleton.Of( "\u0441\u043E\u043E\u0440\u0435\u0433" ) );
// Greek rho-omicron-rho-omicron-nu, which draws "Popov".
Assert.AreEqual(
CharacterNameSkeleton.Of( "Popov" ),
CharacterNameSkeleton.Of( "\u03C1\u03BF\u03C1\u03BF\u03BD" ) );
}
[TestMethod]
public void FoldingFollowsUnicodeRatherThanLocalIntuition()
{
// A documented residual, asserted so a table bump surfaces it rather than hiding it:
// UTS #39 judges Cyrillic em confusable with a turned w, NOT with Latin m, so a Cyrillic
// name using it does not collapse onto the Latin name it arguably resembles. The script
// profile still blocks the mixed-script form, which is the reachable attack.
Assert.AreNotEqual(
CharacterNameSkeleton.Of( "Maxwell" ),
CharacterNameSkeleton.Of( "\u043C\u0430\u0445\u051D\u0435\u0456\u0456" ) );
// Latin i and l stay distinct - the standard folds the digit 1, capital I and the bar
// onto l, but never merges lowercase i with it.
Assert.AreEqual( CharacterNameSkeleton.Of( "Alice" ), CharacterNameSkeleton.Of( "A1ice" ) );
Assert.AreNotEqual( CharacterNameSkeleton.Of( "Bili" ), CharacterNameSkeleton.Of( "Bill" ) );
}
[TestMethod]
public void DistinctNamesKeepDistinctSkeletons()
{
Assert.AreNotEqual( CharacterNameSkeleton.Of( "Alice" ), CharacterNameSkeleton.Of( "Alicia" ) );
Assert.AreNotEqual( CharacterNameSkeleton.Of( "Alice" ), CharacterNameSkeleton.Of( "Alison" ) );
Assert.AreNotEqual( CharacterNameSkeleton.Of( "Barney" ), CharacterNameSkeleton.Of( "Gordon" ) );
Assert.AreNotEqual( CharacterNameSkeleton.Of( "Judith Mossman" ), CharacterNameSkeleton.Of( "Eli Vance" ) );
// A name in a script with no Latin look-alikes is left alone rather than flattened.
Assert.AreNotEqual(
CharacterNameSkeleton.Of( "\u0410\u043B\u0438\u0441\u0430" ), // Cyrillic "Alisa"
CharacterNameSkeleton.Of( "\u0411\u043E\u0440\u0438\u0441" ) ); // Cyrillic "Boris"
}
[TestMethod]
public void NameWithNoIdentityBearingCharactersHasAnEmptySkeleton()
{
Assert.AreEqual( string.Empty, CharacterNameSkeleton.Of( "..." ) );
Assert.AreEqual( string.Empty, CharacterNameSkeleton.Of( "---" ) );
Assert.AreEqual( string.Empty, CharacterNameSkeleton.Of( string.Empty ) );
}
}