csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
16 KiB
401 lines
16 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using Avalonia.Media;
|
|
using Avalonia.Media.Fonts;
|
|
using Xunit;
|
|
|
|
namespace Avalonia.Base.UnitTests.Media
|
|
{
|
|
public class NormalizedVariationPositionTests
|
|
{
|
|
private static readonly OpenTypeTag Wght = OpenTypeTag.Parse("wght");
|
|
private static readonly OpenTypeTag Wdth = OpenTypeTag.Parse("wdth");
|
|
private static readonly OpenTypeTag Ital = OpenTypeTag.Parse("ital");
|
|
|
|
[Fact]
|
|
public void Default_Struct_Is_The_No_Variation_Case()
|
|
{
|
|
var settings = default(NormalizedVariationPosition);
|
|
|
|
Assert.True(settings.IsDefault);
|
|
Assert.Empty(settings.Coordinates);
|
|
Assert.Equal(0, settings.GetHashCode());
|
|
}
|
|
|
|
[Fact]
|
|
public void Default_Structs_Are_Equal()
|
|
{
|
|
// Two zero-initialized structs must compare equal, even though they're
|
|
// distinct values on the stack. This is the substitute for the previous
|
|
// singleton Default.
|
|
Assert.Equal(default(NormalizedVariationPosition), default(NormalizedVariationPosition));
|
|
Assert.True(default(NormalizedVariationPosition) == default(NormalizedVariationPosition));
|
|
}
|
|
|
|
[Fact]
|
|
public void Coordinates_Property_Returns_Empty_Not_Default_For_Default_Struct()
|
|
{
|
|
// The IsDefault → Empty normalization in the property lets callers iterate
|
|
// / index without first checking ImmutableArray.IsDefault.
|
|
var settings = default(NormalizedVariationPosition);
|
|
|
|
Assert.False(settings.Coordinates.IsDefault);
|
|
Assert.Equal(0, settings.Coordinates.Length);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromCoordinates_Dictionary_Throws_On_Null()
|
|
{
|
|
Assert.Throws<ArgumentNullException>(
|
|
() => NormalizedVariationPosition.FromCoordinates((IReadOnlyDictionary<OpenTypeTag, float>)null!));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(float.NaN)]
|
|
[InlineData(-1.0001f)]
|
|
[InlineData(1.0001f)]
|
|
[InlineData(float.PositiveInfinity)]
|
|
[InlineData(float.NegativeInfinity)]
|
|
public void FromCoordinates_Dictionary_Rejects_Out_Of_Range_Or_NaN(float value)
|
|
{
|
|
var coords = new Dictionary<OpenTypeTag, float> { [Wght] = value };
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(
|
|
() => NormalizedVariationPosition.FromCoordinates(coords));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(-1f)]
|
|
[InlineData(1f)]
|
|
public void FromCoordinates_Dictionary_Accepts_Boundary_Values(float value)
|
|
{
|
|
var coords = new Dictionary<OpenTypeTag, float> { [Wght] = value };
|
|
|
|
var settings = NormalizedVariationPosition.FromCoordinates(coords);
|
|
|
|
Assert.Single(settings.Coordinates);
|
|
Assert.Equal(Wght, settings.Coordinates[0].Axis);
|
|
Assert.Equal(value, settings.Coordinates[0].NormalizedValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromCoordinates_Drops_Zero_Coordinates()
|
|
{
|
|
// 0 is the axis default: an explicit wght=0 must produce the same value (and the
|
|
// same variation-cache key downstream) as settings that omit the axis entirely.
|
|
var fromDictionary = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0f });
|
|
|
|
Assert.True(fromDictionary.IsDefault);
|
|
Assert.Equal(default(NormalizedVariationPosition), fromDictionary);
|
|
|
|
Span<NormalizedVariationCoordinate> coords =
|
|
[
|
|
new NormalizedVariationCoordinate(Wght, 0f),
|
|
new NormalizedVariationCoordinate(Wdth, -0.25f),
|
|
];
|
|
|
|
var fromSpan = NormalizedVariationPosition.FromCoordinates(coords);
|
|
|
|
Assert.Single(fromSpan.Coordinates);
|
|
Assert.Equal(Wdth, fromSpan.Coordinates[0].Axis);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromCoordinates_Span_Rejects_Duplicate_Axes_Even_When_Zero_Valued()
|
|
{
|
|
// Canonicalization must not weaken validation: the duplicate check runs before
|
|
// zero-valued coordinates are dropped.
|
|
Assert.Throws<ArgumentException>(static () =>
|
|
{
|
|
Span<NormalizedVariationCoordinate> coords =
|
|
[
|
|
new NormalizedVariationCoordinate(OpenTypeTag.Parse("wght"), 0f),
|
|
new NormalizedVariationCoordinate(OpenTypeTag.Parse("wght"), 0.5f),
|
|
];
|
|
NormalizedVariationPosition.FromCoordinates(coords);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void FromCoordinates_Dictionary_Empty_Returns_Default_Struct()
|
|
{
|
|
var settings = NormalizedVariationPosition.FromCoordinates(new Dictionary<OpenTypeTag, float>());
|
|
|
|
Assert.True(settings.IsDefault);
|
|
Assert.Equal(default(NormalizedVariationPosition), settings);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromCoordinates_Dictionary_Sorts_By_Axis_Tag()
|
|
{
|
|
// Insertion order shouldn't matter — coordinates land sorted by (uint)tag
|
|
// so equality and hashing are insertion-order-independent.
|
|
var unordered = new Dictionary<OpenTypeTag, float>
|
|
{
|
|
[Wght] = 0.5f,
|
|
[Ital] = 1f,
|
|
[Wdth] = -0.25f,
|
|
};
|
|
|
|
var settings = NormalizedVariationPosition.FromCoordinates(unordered);
|
|
|
|
Assert.Equal(3, settings.Coordinates.Length);
|
|
// Sorted: ital (0x6974616c), wdth (0x77647468), wght (0x77676874).
|
|
// Lexically by the 4-char ASCII tag uint, that's ital < wdth < wght.
|
|
Assert.Equal(Ital, settings.Coordinates[0].Axis);
|
|
Assert.Equal(Wdth, settings.Coordinates[1].Axis);
|
|
Assert.Equal(Wght, settings.Coordinates[2].Axis);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromCoordinates_Dictionary_Defensively_Copies_The_Input()
|
|
{
|
|
var mutable = new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f };
|
|
|
|
var settings = NormalizedVariationPosition.FromCoordinates(mutable);
|
|
|
|
mutable[Wght] = 0.9f;
|
|
mutable[Wdth] = -0.25f;
|
|
|
|
Assert.Single(settings.Coordinates);
|
|
Assert.Equal(0.5f, settings.Coordinates[0].NormalizedValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromCoordinates_Span_Empty_Returns_Default_Struct()
|
|
{
|
|
var settings = NormalizedVariationPosition.FromCoordinates(ReadOnlySpan<NormalizedVariationCoordinate>.Empty);
|
|
|
|
Assert.True(settings.IsDefault);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromCoordinates_Span_Sorts_And_Validates()
|
|
{
|
|
Span<NormalizedVariationCoordinate> coords =
|
|
[
|
|
new NormalizedVariationCoordinate(Wght, 0.5f),
|
|
new NormalizedVariationCoordinate(Ital, 1f),
|
|
new NormalizedVariationCoordinate(Wdth, -0.25f),
|
|
];
|
|
|
|
var settings = NormalizedVariationPosition.FromCoordinates(coords);
|
|
|
|
Assert.Equal(3, settings.Coordinates.Length);
|
|
Assert.Equal(Ital, settings.Coordinates[0].Axis);
|
|
Assert.Equal(Wdth, settings.Coordinates[1].Axis);
|
|
Assert.Equal(Wght, settings.Coordinates[2].Axis);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromCoordinates_Span_Rejects_Duplicate_Axes()
|
|
{
|
|
Assert.Throws<ArgumentException>(static () =>
|
|
{
|
|
Span<NormalizedVariationCoordinate> coords =
|
|
[
|
|
new NormalizedVariationCoordinate(OpenTypeTag.Parse("wght"), 0.5f),
|
|
new NormalizedVariationCoordinate(OpenTypeTag.Parse("wght"), -0.5f),
|
|
];
|
|
NormalizedVariationPosition.FromCoordinates(coords);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void FromCoordinates_Span_Rejects_Out_Of_Range_Value()
|
|
{
|
|
Assert.Throws<ArgumentOutOfRangeException>(static () =>
|
|
{
|
|
Span<NormalizedVariationCoordinate> coords = [new NormalizedVariationCoordinate(OpenTypeTag.Parse("wght"), 2f)];
|
|
NormalizedVariationPosition.FromCoordinates(coords);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetCoordinate_Returns_True_For_Present_Axis()
|
|
{
|
|
var settings = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f, [Wdth] = -0.25f });
|
|
|
|
Assert.True(settings.TryGetCoordinate(Wght, out var w));
|
|
Assert.Equal(0.5f, w);
|
|
|
|
Assert.True(settings.TryGetCoordinate(Wdth, out var wd));
|
|
Assert.Equal(-0.25f, wd);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetCoordinate_Returns_False_For_Absent_Axis()
|
|
{
|
|
var settings = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f });
|
|
|
|
Assert.False(settings.TryGetCoordinate(Ital, out var v));
|
|
Assert.Equal(0f, v);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryGetCoordinate_Returns_False_For_Default_Struct()
|
|
{
|
|
var settings = default(NormalizedVariationPosition);
|
|
|
|
Assert.False(settings.TryGetCoordinate(Wght, out var v));
|
|
Assert.Equal(0f, v);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetCoordinateOrDefault_Returns_Fallback_For_Absent_Axis()
|
|
{
|
|
var settings = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f });
|
|
|
|
Assert.Equal(0.5f, settings.GetCoordinateOrDefault(Wght));
|
|
Assert.Equal(0f, settings.GetCoordinateOrDefault(Ital));
|
|
Assert.Equal(-1f, settings.GetCoordinateOrDefault(Ital, -1f));
|
|
}
|
|
|
|
[Fact]
|
|
public void Equality_Is_Reflexive()
|
|
{
|
|
var settings = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f });
|
|
|
|
Assert.True(settings.Equals(settings));
|
|
}
|
|
|
|
[Fact]
|
|
public void Equality_Is_Structural_For_Identical_Coordinates()
|
|
{
|
|
var a = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f, [Wdth] = -0.25f });
|
|
var b = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f, [Wdth] = -0.25f });
|
|
|
|
Assert.True(a.Equals(b));
|
|
Assert.True(b.Equals(a));
|
|
Assert.Equal(a.GetHashCode(), b.GetHashCode());
|
|
}
|
|
|
|
[Fact]
|
|
public void Equality_Ignores_Insertion_Order()
|
|
{
|
|
var a = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f, [Wdth] = -0.25f });
|
|
var b = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wdth] = -0.25f, [Wght] = 0.5f });
|
|
|
|
Assert.True(a.Equals(b));
|
|
Assert.Equal(a.GetHashCode(), b.GetHashCode());
|
|
}
|
|
|
|
[Fact]
|
|
public void Equality_Differs_When_A_Coordinate_Value_Differs()
|
|
{
|
|
var a = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f });
|
|
var b = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.6f });
|
|
|
|
Assert.False(a.Equals(b));
|
|
}
|
|
|
|
[Fact]
|
|
public void Equality_Differs_When_A_Coordinate_Key_Differs()
|
|
{
|
|
var a = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f });
|
|
var b = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wdth] = 0.5f });
|
|
|
|
Assert.False(a.Equals(b));
|
|
}
|
|
|
|
[Fact]
|
|
public void Equality_Differs_When_Coordinate_Counts_Differ()
|
|
{
|
|
// The second coordinate must be non-zero: zero coordinates canonicalize away in
|
|
// FromCoordinates, which would make these two values deliberately equal.
|
|
var a = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f });
|
|
var b = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f, [Wdth] = -0.25f });
|
|
|
|
Assert.False(a.Equals(b));
|
|
}
|
|
|
|
[Fact]
|
|
public void Equality_Differs_Between_Default_And_Populated()
|
|
{
|
|
var a = default(NormalizedVariationPosition);
|
|
var b = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f });
|
|
|
|
Assert.False(a.Equals(b));
|
|
Assert.False(b.Equals(a));
|
|
}
|
|
|
|
[Fact]
|
|
public void Hash_Is_Cached_And_Stable_Across_Equal_Instances()
|
|
{
|
|
// The hash is computed once at construction. Two structurally-equal settings
|
|
// must have the same hash regardless of how the coordinates were inserted.
|
|
var a = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f, [Wdth] = -0.25f, [Ital] = 1f });
|
|
var b = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Ital] = 1f, [Wght] = 0.5f, [Wdth] = -0.25f });
|
|
|
|
var hashA = a.GetHashCode();
|
|
// Subsequent calls return the same cached value.
|
|
Assert.Equal(hashA, a.GetHashCode());
|
|
Assert.Equal(hashA, b.GetHashCode());
|
|
}
|
|
|
|
[Fact]
|
|
public void Equality_Operators_Match_Equals()
|
|
{
|
|
var a = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Ital] = 1f });
|
|
var b = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Ital] = 1f });
|
|
var c = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Ital] = 0f });
|
|
|
|
Assert.True(a == b);
|
|
Assert.False(a != b);
|
|
Assert.False(a == c);
|
|
Assert.True(a != c);
|
|
}
|
|
|
|
[Fact]
|
|
public void Equality_With_Boxed_Object()
|
|
{
|
|
// Cache-key paths box rarely, but Equals(object) must still be correct.
|
|
var a = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f });
|
|
object boxed = NormalizedVariationPosition.FromCoordinates(
|
|
new Dictionary<OpenTypeTag, float> { [Wght] = 0.5f });
|
|
|
|
Assert.True(a.Equals(boxed));
|
|
Assert.False(a.Equals((object?)null));
|
|
Assert.False(a.Equals("not a settings"));
|
|
}
|
|
|
|
[Fact]
|
|
public void NormalizedVariationCoordinate_Has_Structural_Equality()
|
|
{
|
|
// The coordinate record-struct provides equality for free; verify it
|
|
// behaves as expected so callers can use it in their own comparisons.
|
|
var a = new NormalizedVariationCoordinate(Wght, 0.5f);
|
|
var b = new NormalizedVariationCoordinate(Wght, 0.5f);
|
|
var c = new NormalizedVariationCoordinate(Wght, 0.6f);
|
|
var d = new NormalizedVariationCoordinate(Wdth, 0.5f);
|
|
|
|
Assert.Equal(a, b);
|
|
Assert.NotEqual(a, c);
|
|
Assert.NotEqual(a, d);
|
|
Assert.True(a == b);
|
|
Assert.True(a != c);
|
|
}
|
|
}
|
|
}
|
|
|