using Avalonia.Media.TextFormatting.Unicode;
using Xunit;
namespace Avalonia.Base.UnitTests.Media.TextFormatting;
///
/// Spot-checks for . The trie generator already round-trips
/// every assigned codepoint against its source dictionary, so these tests focus on
/// pinning the public surface against drift: shift/mask layout, default values for
/// unassigned codepoints, and a few well-known codepoints across the four tries.
///
public class UnicodeDataTests
{
[Theory]
[InlineData(0x0061u, GeneralCategory.LowercaseLetter)] // 'a'
[InlineData(0x0041u, GeneralCategory.UppercaseLetter)] // 'A'
[InlineData(0x0030u, GeneralCategory.DecimalNumber)] // '0'
[InlineData(0x0020u, GeneralCategory.SpaceSeparator)] // ' '
[InlineData(0x000Au, GeneralCategory.Control)] // '\n'
[InlineData(0x0009u, GeneralCategory.Control)] // '\t'
[InlineData(0x002Eu, GeneralCategory.OtherPunctuation)] // '.'
[InlineData(0x0028u, GeneralCategory.OpenPunctuation)] // '('
[InlineData(0x0029u, GeneralCategory.ClosePunctuation)] // ')'
[InlineData(0x0024u, GeneralCategory.CurrencySymbol)] // '$'
[InlineData(0x002Bu, GeneralCategory.MathSymbol)] // '+'
[InlineData(0x200Bu, GeneralCategory.Format)] // ZWSP
[InlineData(0xE000u, GeneralCategory.PrivateUse)] // BMP PUA start
[InlineData(0xD800u, GeneralCategory.Surrogate)] // high surrogate start (LSCP path)
[InlineData(0xDFFFu, GeneralCategory.Surrogate)] // low surrogate end (LSCP path)
// Note: codepoints >= HighStart (currently 0x100000) all collapse to a single
// fallback value because the trie compresses Plane 16 to save space. The
// resulting GeneralCategory is not the per-codepoint UCD value. See
// UnicodeTrieTests.Get_AtAndAboveHighStart_AllCodepointsShareFallback.
public void GetGeneralCategory_KnownCodepoints(uint codepoint, GeneralCategory expected)
{
Assert.Equal(expected, UnicodeData.GetGeneralCategory(codepoint));
}
[Theory]
[InlineData(0x0061u, Script.Latin)] // 'a'
[InlineData(0x0041u, Script.Latin)] // 'A'
[InlineData(0x044Fu, Script.Cyrillic)] // 'я'
[InlineData(0x4E2Du, Script.Han)] // '中'
[InlineData(0x05D0u, Script.Hebrew)] // 'א'
[InlineData(0x0627u, Script.Arabic)] // 'ا'
[InlineData(0x0020u, Script.Common)] // ' '
[InlineData(0x0030u, Script.Common)] // '0'
[InlineData(0x1F600u, Script.Common)] // 😀 (supplementary, common)
[InlineData(0x0300u, Script.Inherited)] // combining grave (inherited)
public void GetScript_KnownCodepoints(uint codepoint, Script expected)
{
Assert.Equal(expected, UnicodeData.GetScript(codepoint));
}
[Theory]
[InlineData(0x0061u, BidiClass.LeftToRight)] // 'a'
[InlineData(0x0041u, BidiClass.LeftToRight)] // 'A'
[InlineData(0x0039u, BidiClass.EuropeanNumber)] // '9'
[InlineData(0x0024u, BidiClass.EuropeanTerminator)] // '$'
[InlineData(0x002Cu, BidiClass.CommonSeparator)] // ','
[InlineData(0x0020u, BidiClass.WhiteSpace)] // ' '
[InlineData(0x0009u, BidiClass.SegmentSeparator)] // '\t'
[InlineData(0x000Au, BidiClass.ParagraphSeparator)] // '\n'
[InlineData(0x05D0u, BidiClass.RightToLeft)] // 'א'
[InlineData(0x0627u, BidiClass.ArabicLetter)] // 'ا'
public void GetBiDiClass_KnownCodepoints(uint codepoint, BidiClass expected)
{
Assert.Equal(expected, UnicodeData.GetBiDiClass(codepoint));
}
[Theory]
[InlineData(0x0028u, BidiPairedBracketType.Open, 0x0029u)] // '(' → ')'
[InlineData(0x0029u, BidiPairedBracketType.Close, 0x0028u)] // ')' → '('
[InlineData(0x005Bu, BidiPairedBracketType.Open, 0x005Du)] // '[' → ']'
[InlineData(0x005Du, BidiPairedBracketType.Close, 0x005Bu)] // ']' → '['
[InlineData(0x007Bu, BidiPairedBracketType.Open, 0x007Du)] // '{' → '}'
public void GetBiDiPairedBracket_RoundTripsKnownPairs(uint codepoint, BidiPairedBracketType expectedType, uint expectedPair)
{
Assert.Equal(expectedType, UnicodeData.GetBiDiPairedBracketType(codepoint));
Assert.Equal(expectedPair, UnicodeData.GetBiDiPairedBracket(codepoint).Value);
}
[Fact]
public void GetBiDiPairedBracketType_NonBracket_IsNone()
{
Assert.Equal(BidiPairedBracketType.None, UnicodeData.GetBiDiPairedBracketType(0x0061u));
Assert.Equal(BidiPairedBracketType.None, UnicodeData.GetBiDiPairedBracketType(0x0020u));
}
[Theory]
[InlineData(0x0061u, LineBreakClass.Alphabetic)] // 'a'
[InlineData(0x000Au, LineBreakClass.LineFeed)] // '\n'
[InlineData(0x000Du, LineBreakClass.CarriageReturn)] // '\r'
[InlineData(0x0020u, LineBreakClass.Space)] // ' '
[InlineData(0x0009u, LineBreakClass.BreakAfter)] // '\t'
[InlineData(0x002Du, LineBreakClass.Hyphen)] // '-'
[InlineData(0x0028u, LineBreakClass.OpenPunctuation)] // '('
[InlineData(0x0029u, LineBreakClass.CloseParenthesis)]// ')'
[InlineData(0x0030u, LineBreakClass.Numeric)] // '0'
[InlineData(0x4E2Du, LineBreakClass.Ideographic)] // '中'
[InlineData(0x2028u, LineBreakClass.MandatoryBreak)] // LINE SEPARATOR
[InlineData(0x2029u, LineBreakClass.MandatoryBreak)] // PARAGRAPH SEPARATOR
public void GetLineBreakClass_KnownCodepoints(uint codepoint, LineBreakClass expected)
{
Assert.Equal(expected, UnicodeData.GetLineBreakClass(codepoint));
}
[Theory]
[InlineData(0x0061u, WordBreakClass.ALetter)] // 'a'
[InlineData(0x000Du, WordBreakClass.CarriageReturn)] // '\r'
[InlineData(0x000Au, WordBreakClass.LineFeed)] // '\n'
[InlineData(0x0020u, WordBreakClass.WSegSpace)] // ' '
[InlineData(0x0030u, WordBreakClass.Numeric)] // '0'
[InlineData(0x200Du, WordBreakClass.ZWJ)] // ZWJ
[InlineData(0x05D0u, WordBreakClass.HebrewLetter)] // 'א'
[InlineData(0x4E2Du, WordBreakClass.Other)] // '中' (CJK is WB=Other)
public void GetWordBreakClass_KnownCodepoints(uint codepoint, WordBreakClass expected)
{
Assert.Equal(expected, UnicodeData.GetWordBreakClass(codepoint));
}
[Theory]
[InlineData(0x000Du, GraphemeBreakClass.CR)] // '\r'
[InlineData(0x000Au, GraphemeBreakClass.LF)] // '\n'
[InlineData(0x200Du, GraphemeBreakClass.ZWJ)] // ZWJ
[InlineData(0x1F600u, GraphemeBreakClass.ExtendedPictographic)] // 😀 (overridden by emoji-data.txt)
[InlineData(0x1100u, GraphemeBreakClass.L)] // HANGUL CHOSEONG KIYEOK
[InlineData(0x1161u, GraphemeBreakClass.V)] // HANGUL JUNGSEONG A
[InlineData(0x11A8u, GraphemeBreakClass.T)] // HANGUL JONGSEONG KIYEOK
[InlineData(0x0061u, GraphemeBreakClass.Other)] // 'a'
[InlineData(0x0030u, GraphemeBreakClass.Other)] // '0'
public void GetGraphemeClusterBreak_KnownCodepoints(uint codepoint, GraphemeBreakClass expected)
{
Assert.Equal(expected, UnicodeData.GetGraphemeClusterBreak(codepoint));
}
[Theory]
[InlineData(0x0061u, EastAsianWidthClass.Narrow)] // 'a'
[InlineData(0x0020u, EastAsianWidthClass.Narrow)] // ' '
[InlineData(0x4E2Du, EastAsianWidthClass.Wide)] // '中'
[InlineData(0xFF21u, EastAsianWidthClass.Fullwidth)] // 'A' FULLWIDTH LATIN CAPITAL A
[InlineData(0xFF71u, EastAsianWidthClass.Halfwidth)] // 'ア' HALFWIDTH KATAKANA A
[InlineData(0x03B1u, EastAsianWidthClass.Ambiguous)] // 'α'
[InlineData(0x200Bu, EastAsianWidthClass.Neutral)] // ZWSP
public void GetEastAsianWidthClass_KnownCodepoints(uint codepoint, EastAsianWidthClass expected)
{
Assert.Equal(expected, UnicodeData.GetEastAsianWidthClass(codepoint));
}
///
/// Regression test for the BiDi / GraphemeBreak / UnicodeData trie builders'
/// reliance on the seeded default class sitting at int position 0 (caught at
/// generation time by the ABI validator, but only this asserts the runtime
/// behavior of an unassigned codepoint).
///
[Fact]
public void UnassignedCodepoint_FallsBackToSeededDefaults()
{
// U+0378 is an unassigned BMP code point (and has been for decades — stable choice).
const uint unassigned = 0x0378u;
// Default Bidi class for unassigned codepoints is LeftToRight (seeded at position 0).
Assert.Equal(BidiClass.LeftToRight, UnicodeData.GetBiDiClass(unassigned));
// Default grapheme break class is Other (seeded at position 0).
Assert.Equal(GraphemeBreakClass.Other, UnicodeData.GetGraphemeClusterBreak(unassigned));
// Default line break class is Unknown — set explicitly via initialValue in the
// UnicodeData trie builder, not via seed position 0.
Assert.Equal(LineBreakClass.Unknown, UnicodeData.GetLineBreakClass(unassigned));
// Default word break class is Other — also set explicitly in the generator's
// post-pass that maps unset WordBreakClass to Other.
Assert.Equal(WordBreakClass.Other, UnicodeData.GetWordBreakClass(unassigned));
}
}