Browse Source

Merge pull request #11169 from AvaloniaUI/unify-test-mocks

[WIP] Reuse text and geometry related headless mocks in Avalonia unit test and benchmarks
pull/11146/head
Max Katz 3 years ago
committed by GitHub
parent
commit
08641748f9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      src/Headless/Avalonia.Headless/Avalonia.Headless.csproj
  2. 96
      src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  3. 109
      src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs
  4. 7
      tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs
  5. 5
      tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
  6. 1
      tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs
  7. 285
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  8. 7
      tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs
  9. 17
      tests/Avalonia.Benchmarks/NullCursorFactory.cs
  10. 107
      tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs
  11. 149
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  12. 27
      tests/Avalonia.Benchmarks/NullThreadingPlatform.cs
  13. 5
      tests/Avalonia.Benchmarks/Rendering/ShapeRendering.cs
  14. 5
      tests/Avalonia.Benchmarks/Styling/ControlTheme_Change.cs
  15. 5
      tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs
  16. 5
      tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs
  17. 5
      tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs
  18. 3
      tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs
  19. 7
      tests/Avalonia.Controls.UnitTests/DatePickerTests.cs
  20. 7
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  21. 13
      tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
  22. 7
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  23. 11
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  24. 5
      tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs
  25. 7
      tests/Avalonia.Controls.UnitTests/TimePickerTests.cs
  26. 7
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  27. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  28. 3
      tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs
  29. 1
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  30. 1
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
  31. 1
      tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj
  32. 62
      tests/Avalonia.UnitTests/ImmediateDispatcher.cs
  33. 63
      tests/Avalonia.UnitTests/MockFontManagerImpl.cs
  34. 34
      tests/Avalonia.UnitTests/MockGlyphRun.cs
  35. 81
      tests/Avalonia.UnitTests/MockGlyphTypeface.cs
  36. 174
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  37. 179
      tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
  38. 38
      tests/Avalonia.UnitTests/MockTextShaperImpl.cs
  39. 34
      tests/Avalonia.UnitTests/TestServices.cs
  40. 15
      tests/Avalonia.UnitTests/TextTestHelper.cs

9
src/Headless/Avalonia.Headless/Avalonia.Headless.csproj

@ -12,7 +12,16 @@
<Import Project="..\..\..\build\TrimmingEnable.props" />
<Import Project="..\..\..\build\NullableEnable.props" />
<ItemGroup>
<Compile Remove="..\..\Shared\ModuleInitializer.cs" />
</ItemGroup>
<ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.Headless.Vnc, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Base.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
</ItemGroup>
</Project>

96
src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -129,18 +129,30 @@ namespace Avalonia.Headless
Point baselineOrigin,
Rect bounds)
{
return new HeadlessGlyphRunStub();
return new HeadlessGlyphRunStub(glyphTypeface, fontRenderingEmSize, baselineOrigin, bounds);
}
private class HeadlessGlyphRunStub : IGlyphRunImpl
internal class HeadlessGlyphRunStub : IGlyphRunImpl
{
public Rect Bounds => new Rect(new Size(8, 12));
public HeadlessGlyphRunStub(
IGlyphTypeface glyphTypeface,
double fontRenderingEmSize,
Point baselineOrigin,
Rect bounds)
{
GlyphTypeface = glyphTypeface;
FontRenderingEmSize = fontRenderingEmSize;
BaselineOrigin = baselineOrigin;
Bounds =bounds;
}
public Point BaselineOrigin => new Point(0, 8);
public Rect Bounds { get; }
public IGlyphTypeface GlyphTypeface => new HeadlessGlyphTypefaceImpl();
public Point BaselineOrigin { get; }
public double FontRenderingEmSize => 12;
public IGlyphTypeface GlyphTypeface { get; }
public double FontRenderingEmSize { get; }
public void Dispose()
{
@ -235,8 +247,11 @@ namespace Avalonia.Headless
private class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl
{
private HeadlessStreamingGeometryContextStub _context;
public HeadlessStreamingGeometryStub() : base(default)
{
_context = new HeadlessStreamingGeometryContextStub(this);
}
public IStreamGeometryImpl Clone()
@ -246,13 +261,18 @@ namespace Avalonia.Headless
public IStreamGeometryContextImpl Open()
{
return new HeadlessStreamingGeometryContextStub(this);
return _context;
}
public override bool FillContains(Point point)
{
return _context.FillContains(point);
}
private class HeadlessStreamingGeometryContextStub : IStreamGeometryContextImpl
{
private readonly HeadlessStreamingGeometryStub _parent;
private double _x1, _y1, _x2, _y2;
private List<Point> points = new List<Point>();
public HeadlessStreamingGeometryContextStub(HeadlessStreamingGeometryStub parent)
{
_parent = parent;
@ -260,19 +280,30 @@ namespace Avalonia.Headless
private void Track(Point pt)
{
if (_x1 > pt.X)
_x1 = pt.X;
if (_x2 < pt.X)
_x2 = pt.X;
if (_y1 > pt.Y)
_y1 = pt.Y;
if (_y2 < pt.Y)
_y2 = pt.Y;
points.Add(pt);
}
public Rect CalculateBounds()
{
var left = double.MaxValue;
var right = double.MinValue;
var top = double.MaxValue;
var bottom = double.MinValue;
foreach (var p in points)
{
left = Math.Min(p.X, left);
right = Math.Max(p.X, right);
top = Math.Min(p.Y, top);
bottom = Math.Max(p.Y, bottom);
}
return new Rect(new Point(left, top), new Point(right, bottom));
}
public void Dispose()
{
_parent.Bounds = new Rect(_x1, _y1, _x2 - _x1, _y2 - _y1);
_parent.Bounds = CalculateBounds();
}
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
@ -304,6 +335,35 @@ namespace Avalonia.Headless
{
}
public bool FillContains(Point point)
{
// Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++)
{
var a = points[i];
var b = points[(i + 1) % points.Count];
var c = points[(i + 2) % points.Count];
Vector v0 = c - a;
Vector v1 = b - a;
Vector v2 = point - a;
var dot00 = v0 * v0;
var dot01 = v0 * v1;
var dot02 = v0 * v2;
var dot11 = v1 * v1;
var dot12 = v1 * v2;
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
if ((u >= 0) && (v >= 0) && (u + v < 1)) return true;
}
return false;
}
}
}
@ -369,7 +429,7 @@ namespace Avalonia.Headless
}
}
private class HeadlessDrawingContextStub : IDrawingContextImpl
internal class HeadlessDrawingContextStub : IDrawingContextImpl
{
public void Dispose()
{

109
src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
@ -11,6 +13,7 @@ using Avalonia.Input.Platform;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
@ -82,22 +85,22 @@ namespace Avalonia.Headless
{
public FontMetrics Metrics => new FontMetrics
{
DesignEmHeight = 1,
Ascent = 8,
Descent = 4,
DesignEmHeight = 10,
Ascent = 2,
Descent = 10,
IsFixedPitch = true,
LineGap = 0,
UnderlinePosition = 2,
UnderlineThickness = 1,
StrikethroughPosition = 2,
StrikethroughThickness = 1,
IsFixedPitch = true
StrikethroughThickness = 1
};
public int GlyphCount => 1337;
public FontSimulations FontSimulations { get; }
public FontSimulations FontSimulations => FontSimulations.None;
public string FamilyName => "Arial";
public string FamilyName => "$Default";
public FontWeight Weight => FontWeight.Normal;
@ -111,24 +114,31 @@ namespace Avalonia.Headless
public ushort GetGlyph(uint codepoint)
{
return 1;
return (ushort)codepoint;
}
public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = 1;
glyph = 8;
return true;
}
public int GetGlyphAdvance(ushort glyph)
{
return 12;
return 8;
}
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{
return glyphs.ToArray().Select(x => (int)x).ToArray();
var advances = new int[glyphs.Length];
for (var i = 0; i < advances.Length; i++)
{
advances[i] = 8;
}
return advances;
}
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints)
@ -146,8 +156,8 @@ namespace Avalonia.Headless
{
metrics = new GlyphMetrics
{
Height = 10,
Width = 8
Width = 10,
Height = 10
};
return true;
@ -161,40 +171,81 @@ namespace Avalonia.Headless
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidiLevel;
var shapedBuffer = new ShapedBuffer(text, text.Length, typeface, fontRenderingEmSize, bidiLevel);
var textSpan = text.Span;
var textStartIndex = TextTestHelper.GetStartCharIndex(text);
for (var i = 0; i < shapedBuffer.Length;)
{
var glyphCluster = i + textStartIndex;
var codepoint = Codepoint.ReadAt(textSpan, i, out var count);
var glyphIndex = typeface.GetGlyph(codepoint);
return new ShapedBuffer(text, text.Length, typeface, fontRenderingEmSize, bidiLevel);
for (var j = 0; j < count; ++j)
{
shapedBuffer[i + j] = new GlyphInfo(glyphIndex, glyphCluster, 10);
}
i += count;
}
return shapedBuffer;
}
}
internal class HeadlessFontManagerStub : IFontManagerImpl
{
private readonly string _defaultFamilyName;
public HeadlessFontManagerStub(string defaultFamilyName = "Default")
{
_defaultFamilyName = defaultFamilyName;
}
public int TryCreateGlyphTypefaceCount { get; private set; }
public string GetDefaultFontFamilyName()
{
return "Arial";
return _defaultFamilyName;
}
public string[] GetInstalledFontFamilyNames(bool checkForUpdates = false)
string[] IFontManagerImpl.GetInstalledFontFamilyNames(bool checkForUpdates)
{
return new string[] { "Arial" };
return new[] { _defaultFamilyName };
}
public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, out IGlyphTypeface glyphTypeface)
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
FontStretch fontStretch,
CultureInfo? culture, out Typeface fontKey)
{
glyphTypeface= new HeadlessGlyphTypefaceImpl();
fontKey = new Typeface(_defaultFamilyName);
return true;
return false;
}
public bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface)
public virtual bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface)
{
glyphTypeface = new HeadlessGlyphTypefaceImpl();
glyphTypeface = null;
TryCreateGlyphTypefaceCount++;
if (familyName == "Unknown")
{
return false;
}
glyphTypeface = new HeadlessGlyphTypefaceImpl();
return true;
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, out Typeface typeface)
public virtual bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface)
{
typeface = new Typeface("Arial", fontStyle, fontWeight, fontStretch);
glyphTypeface = new HeadlessGlyphTypefaceImpl();
return true;
}
}
@ -249,4 +300,14 @@ namespace Avalonia.Headless
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
}
internal static class TextTestHelper
{
public static int GetStartCharIndex(ReadOnlyMemory<char> text)
{
if (!MemoryMarshal.TryGetString(text, out _, out var start, out _))
throw new InvalidOperationException("text memory should have been a string");
return start;
}
}
}

7
tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Headless;
using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;
@ -27,7 +28,7 @@ namespace Avalonia.Base.UnitTests.Media
[Fact]
public void Should_Throw_When_Default_FamilyName_Is_Null()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new MockFontManagerImpl(null))))
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: new HeadlessFontManagerStub(null!))))
{
Assert.Throws<InvalidOperationException>(() => FontManager.Current);
}
@ -39,7 +40,7 @@ namespace Avalonia.Base.UnitTests.Media
var options = new FontManagerOptions { DefaultFamilyName = "MyFont" };
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
.With(fontManagerImpl: new MockFontManagerImpl())))
.With(fontManagerImpl: new HeadlessFontManagerStub())))
{
AvaloniaLocator.CurrentMutable.Bind<FontManagerOptions>().ToConstant(options);
@ -62,7 +63,7 @@ namespace Avalonia.Base.UnitTests.Media
};
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
.With(fontManagerImpl: new MockFontManagerImpl())))
.With(fontManagerImpl: new HeadlessFontManagerStub())))
{
AvaloniaLocator.CurrentMutable.Bind<FontManagerOptions>().ToConstant(options);

5
tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Headless;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.UnitTests;
@ -179,13 +180,13 @@ namespace Avalonia.Base.UnitTests.Media
glyphInfos[i] = new GlyphInfo(0, glyphClusters[i], glyphAdvances[i]);
}
return new GlyphRun(new MockGlyphTypeface(), 10, new string('a', count).AsMemory(), glyphInfos, biDiLevel: bidiLevel);
return new GlyphRun(new HeadlessGlyphTypefaceImpl(), 10, new string('a', count).AsMemory(), glyphInfos, biDiLevel: bidiLevel);
}
private static IDisposable Start()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(
renderInterface: new MockPlatformRenderInterface()));
renderInterface: new HeadlessPlatformRenderInterface()));
}
}
}

1
tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs

@ -1,5 +1,6 @@
using System;
using System.Linq;
using Avalonia.Base.UnitTests.VisualTree;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Shapes;

285
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@ -1,285 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Avalonia.Media.Imaging;
using Avalonia.Media.TextFormatting;
namespace Avalonia.Base.UnitTests.VisualTree
{
class MockRenderInterface : IPlatformRenderInterface, IPlatformRenderInterfaceContext
{
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
throw new NotImplementedException();
}
public bool IsLost => false;
public object TryGetFeature(Type featureType) => null;
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
{
throw new NotImplementedException();
}
public IStreamGeometryImpl CreateStreamGeometry()
{
return new MockStreamGeometry();
}
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<IGeometryImpl> children)
{
throw new NotImplementedException();
}
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, IGeometryImpl g1, IGeometryImpl g2)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(string fileName)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{
throw new NotImplementedException();
}
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{
throw new NotImplementedException();
}
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext)
{
return this;
}
public bool SupportsIndividualRoundRects { get; set; }
public AlphaFormat DefaultAlphaFormat { get; }
public PixelFormat DefaultPixelFormat { get; }
public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
public IFontManagerImpl CreateFontManager()
{
return new MockFontManagerImpl();
}
public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat fmt, AlphaFormat alphaFormat)
{
throw new NotImplementedException();
}
public IGeometryImpl CreateEllipseGeometry(Rect rect)
{
throw new NotImplementedException();
}
public IGeometryImpl CreateLineGeometry(Point p1, Point p2)
{
throw new NotImplementedException();
}
public IGeometryImpl CreateRectangleGeometry(Rect rect)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
throw new NotImplementedException();
}
class MockStreamGeometry : IStreamGeometryImpl
{
private MockStreamGeometryContext _impl = new MockStreamGeometryContext();
public Rect Bounds
{
get
{
throw new NotImplementedException();
}
}
public double ContourLength { get; }
public IStreamGeometryImpl Clone()
{
return this;
}
public void Dispose()
{
}
public bool FillContains(Point point)
{
return _impl.FillContains(point);
}
public Rect GetRenderBounds(IPen pen)
{
throw new NotImplementedException();
}
public IGeometryImpl Intersect(IGeometryImpl geometry)
{
throw new NotImplementedException();
}
public IStreamGeometryContextImpl Open()
{
return _impl;
}
public bool StrokeContains(IPen pen, Point point)
{
throw new NotImplementedException();
}
public ITransformedGeometryImpl WithTransform(Matrix transform)
{
throw new NotImplementedException();
}
public bool TryGetPointAtDistance(double distance, out Point point)
{
throw new NotImplementedException();
}
public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
{
throw new NotImplementedException();
}
public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
{
throw new NotImplementedException();
}
class MockStreamGeometryContext : IStreamGeometryContextImpl
{
private List<Point> points = new List<Point>();
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
throw new NotImplementedException();
}
public void BeginFigure(Point startPoint, bool isFilled)
{
points.Add(startPoint);
}
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
throw new NotImplementedException();
}
public void Dispose()
{
}
public void EndFigure(bool isClosed)
{
}
public void LineTo(Point point)
{
points.Add(point);
}
public void QuadraticBezierTo(Point control, Point endPoint)
{
throw new NotImplementedException();
}
public void SetFillRule(FillRule fillRule)
{
}
public bool FillContains(Point point)
{
// Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++)
{
var a = points[i];
var b = points[(i + 1) % points.Count];
var c = points[(i + 2) % points.Count];
Vector v0 = c - a;
Vector v1 = b - a;
Vector v2 = point - a;
var dot00 = v0 * v0;
var dot01 = v0 * v1;
var dot02 = v0 * v2;
var dot11 = v1 * v1;
var dot12 = v1 * v2;
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
if ((u >= 0) && (v >= 0) && (u + v < 1)) return true;
}
return false;
}
}
}
public void Dispose()
{
}
}
}

7
tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs

@ -1,6 +1,7 @@
using System;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Threading;
using Avalonia.UnitTests;
using BenchmarkDotNet.Attributes;
@ -15,11 +16,7 @@ namespace Avalonia.Benchmarks.Layout
public ControlsBenchmark()
{
_app = UnitTestApplication.Start(
TestServices.StyledWindow.With(
renderInterface: new NullRenderingPlatform(),
dispatcherImpl: new NullThreadingPlatform(),
standardCursorFactory: new NullCursorFactory()));
_app = UnitTestApplication.Start(TestServices.StyledWindow);
_root = new TestRoot(true, null)
{

17
tests/Avalonia.Benchmarks/NullCursorFactory.cs

@ -1,17 +0,0 @@
using System;
using Avalonia.Input;
using Avalonia.Platform;
namespace Avalonia.Benchmarks
{
internal class NullCursorFactory : ICursorFactory
{
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new NullCursorImpl();
ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) => new NullCursorImpl();
private class NullCursorImpl : ICursorImpl
{
public void Dispose() { }
}
}
}

107
tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs

@ -1,107 +0,0 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;
namespace Avalonia.Benchmarks
{
internal class NullDrawingContextImpl : IDrawingContextImpl
{
public void Dispose()
{
}
public Matrix Transform { get; set; }
public RenderOptions RenderOptions { get; set; }
public void Clear(Color color)
{
}
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{
}
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
}
public void DrawLine(IPen pen, Point p1, Point p2)
{
}
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
}
public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadows = default)
{
}
public void DrawEllipse(IBrush brush, IPen pen, Rect rect)
{
}
public void DrawGlyphRun(IBrush foreground, IRef<IGlyphRunImpl> glyphRun)
{
}
public IDrawingContextLayerImpl CreateLayer(Size size)
{
return null;
}
public void PushClip(Rect clip)
{
}
public void PushClip(RoundedRect clip)
{
}
public void PopClip()
{
}
public void PushOpacity(double opacity, Rect bounds)
{
}
public void PopOpacity()
{
}
public void PushOpacityMask(IBrush mask, Rect bounds)
{
}
public void PopOpacityMask()
{
}
public void PushGeometryClip(IGeometryImpl clip)
{
}
public void PopGeometryClip()
{
}
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
}
public void PopBitmapBlendMode()
{
}
public void Custom(ICustomDrawOperation custom)
{
}
public object GetFeature(Type t) => null;
}
}

149
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -1,149 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Avalonia.Media.Imaging;
using Avalonia.Media.TextFormatting;
using Microsoft.Diagnostics.Runtime;
namespace Avalonia.Benchmarks
{
internal class NullRenderingPlatform : IPlatformRenderInterface, IPlatformRenderInterfaceContext
{
public IGeometryImpl CreateEllipseGeometry(Rect rect)
{
return new MockStreamGeometryImpl();
}
public IGeometryImpl CreateLineGeometry(Point p1, Point p2)
{
return new MockStreamGeometryImpl();
}
public IGeometryImpl CreateRectangleGeometry(Rect rect)
{
return new MockStreamGeometryImpl();
}
public IStreamGeometryImpl CreateStreamGeometry()
{
return new MockStreamGeometryImpl();
}
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<IGeometryImpl> children)
{
throw new NotImplementedException();
}
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, IGeometryImpl g1, IGeometryImpl g2)
{
throw new NotImplementedException();
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
throw new NotImplementedException();
}
public bool IsLost => false;
public object TryGetFeature(Type featureType) => null;
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat format, AlphaFormat alphaFormat)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(string fileName)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IFontManagerImpl CreateFontManager()
{
return new MockFontManagerImpl();
}
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
return new MockStreamGeometryImpl();
}
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{
return new MockGlyphRun(glyphTypeface, fontRenderingEmSize, baselineOrigin, bounds);
}
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext)
{
return this;
}
public bool SupportsIndividualRoundRects => true;
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
public void Dispose()
{
}
}
}

27
tests/Avalonia.Benchmarks/NullThreadingPlatform.cs

@ -1,27 +0,0 @@
using System;
using System.Reactive.Disposables;
using System.Threading;
using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.Benchmarks
{
internal class NullThreadingPlatform : IDispatcherImpl
{
public void Signal()
{
}
public void UpdateTimer(long? dueTimeInMs)
{
}
public bool CurrentThreadIsLoopThread => true;
#pragma warning disable CS0067
public event Action Signaled;
public event Action Timer;
public long Now => 0;
#pragma warning restore CS0067
}
}

5
tests/Avalonia.Benchmarks/Rendering/ShapeRendering.cs

@ -1,4 +1,5 @@
using Avalonia.Controls.Shapes;
using Avalonia.Headless;
using Avalonia.Media;
using Avalonia.Platform;
using BenchmarkDotNet.Attributes;
@ -21,9 +22,9 @@ namespace Avalonia.Benchmarks.Rendering
_lineFill = new Line { Fill = new SolidColorBrush() };
_lineFillAndStroke = new Line { Stroke = new SolidColorBrush(), Fill = new SolidColorBrush() };
_drawingContext = new PlatformDrawingContext(new NullDrawingContextImpl(), true);
_drawingContext = new PlatformDrawingContext(new HeadlessPlatformRenderInterface.HeadlessDrawingContextStub(), true);
AvaloniaLocator.CurrentMutable.Bind<IPlatformRenderInterface>().ToConstant(new NullRenderingPlatform());
AvaloniaLocator.CurrentMutable.Bind<IPlatformRenderInterface>().ToConstant(new HeadlessPlatformRenderInterface());
}
[Benchmark]

5
tests/Avalonia.Benchmarks/Styling/ControlTheme_Change.cs

@ -20,10 +20,7 @@ namespace Avalonia.Benchmarks.Styling
public ControlTheme_Change()
{
_app = UnitTestApplication.Start(
TestServices.StyledWindow.With(
renderInterface: new NullRenderingPlatform(),
dispatcherImpl: new NullThreadingPlatform()));
_app = UnitTestApplication.Start(TestServices.StyledWindow);
// Simulate an application with a lot of styles by creating a tree of nested panels,
// each with a bunch of styles applied.

5
tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.UnitTests;
@ -20,12 +21,8 @@ namespace Avalonia.Benchmarks.Styling
assetLoader: new StandardAssetLoader(),
globalClock: new MockGlobalClock(),
platform: new AppBuilder().RuntimePlatform,
renderInterface: new MockPlatformRenderInterface(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
theme: () => CreateTheme(),
dispatcherImpl: new NullThreadingPlatform(),
fontManagerImpl: new MockFontManagerImpl(),
textShaperImpl: new MockTextShaperImpl(),
windowingPlatform: new MockWindowingPlatform());
return UnitTestApplication.Start(services);

5
tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs

@ -17,10 +17,7 @@ namespace Avalonia.Benchmarks.Styling
public Style_Apply_Detach_Complex()
{
_app = UnitTestApplication.Start(
TestServices.StyledWindow.With(
renderInterface: new NullRenderingPlatform(),
dispatcherImpl: new NullThreadingPlatform()));
_app = UnitTestApplication.Start(TestServices.StyledWindow);
// Simulate an application with a lot of styles by creating a tree of nested panels,
// each with a bunch of styles applied.

5
tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs

@ -30,10 +30,7 @@ public class HugeTextLayout : IDisposable
{
_manySmallStrings = Enumerable.Range(0, 1000).Select(_ => RandomString(s_rand.Next(2, 15))).ToArray();
var testServices = TestServices.StyledWindow.With(
renderInterface: new NullRenderingPlatform(),
dispatcherImpl: new NullThreadingPlatform(),
standardCursorFactory: new NullCursorFactory());
var testServices = TestServices.StyledWindow;
if (s_useSkia)
{

3
tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs

@ -45,9 +45,6 @@ namespace Avalonia.Benchmarks.Themes
private static IDisposable CreateApp()
{
var services = new TestServices(
renderInterface: new NullRenderingPlatform(),
dispatcherImpl: new NullThreadingPlatform(),
standardCursorFactory: new NullCursorFactory(),
theme: () => LoadFluentTheme());
return UnitTestApplication.Start(services);

7
tests/Avalonia.Controls.UnitTests/DatePickerTests.cs

@ -2,6 +2,7 @@
using System.Linq;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Headless;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
@ -203,10 +204,10 @@ namespace Avalonia.Controls.UnitTests
}
private static TestServices Services => TestServices.MockThreadingInterface.With(
fontManagerImpl: new MockFontManagerImpl(),
fontManagerImpl: new HeadlessFontManagerStub(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
textShaperImpl: new MockTextShaperImpl(),
renderInterface: new MockPlatformRenderInterface());
textShaperImpl: new HeadlessTextShaperStub(),
renderInterface: new HeadlessPlatformRenderInterface());
private static IControlTemplate CreateTemplate()
{

7
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -9,6 +9,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
@ -1022,12 +1023,12 @@ namespace Avalonia.Controls.UnitTests
return UnitTestApplication.Start(
TestServices.MockThreadingInterface.With(
focusManager: new FocusManager(),
fontManagerImpl: new MockFontManagerImpl(),
fontManagerImpl: new HeadlessFontManagerStub(),
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: new KeyboardNavigationHandler(),
inputManager: new InputManager(),
renderInterface: new MockPlatformRenderInterface(),
textShaperImpl: new MockTextShaperImpl()));
renderInterface: new HeadlessPlatformRenderInterface(),
textShaperImpl: new HeadlessTextShaperStub()));
}
private class ItemsControlWithContainer : ItemsControl, IStyleable

13
tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs

@ -7,6 +7,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Layout;
@ -891,16 +892,16 @@ namespace Avalonia.Controls.UnitTests
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: new KeyboardNavigationHandler(),
inputManager: new InputManager(),
renderInterface: new MockPlatformRenderInterface(),
fontManagerImpl: new MockFontManagerImpl(),
textShaperImpl: new MockTextShaperImpl(),
renderInterface: new HeadlessPlatformRenderInterface(),
fontManagerImpl: new HeadlessFontManagerStub(),
textShaperImpl: new HeadlessTextShaperStub(),
standardCursorFactory: Mock.Of<ICursorFactory>());
private static TestServices Services => TestServices.MockThreadingInterface.With(
renderInterface: new MockPlatformRenderInterface(),
renderInterface: new HeadlessPlatformRenderInterface(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
textShaperImpl: new MockTextShaperImpl(),
fontManagerImpl: new MockFontManagerImpl());
textShaperImpl: new HeadlessTextShaperStub(),
fontManagerImpl: new HeadlessFontManagerStub());
private static IControlTemplate CreateTemplate()
{

7
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -10,6 +10,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Styling;
@ -1347,12 +1348,12 @@ namespace Avalonia.Controls.UnitTests.Primitives
return UnitTestApplication.Start(
TestServices.MockThreadingInterface.With(
focusManager: new FocusManager(),
fontManagerImpl: new MockFontManagerImpl(),
fontManagerImpl: new HeadlessFontManagerStub(),
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: new KeyboardNavigationHandler(),
inputManager: new InputManager(),
renderInterface: new MockPlatformRenderInterface(),
textShaperImpl: new MockTextShaperImpl()));
renderInterface: new HeadlessPlatformRenderInterface(),
textShaperImpl: new HeadlessTextShaperStub()));
}
private class TestSelector : SelectingItemsControl

11
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -6,6 +6,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Layout;
@ -1103,14 +1104,14 @@ namespace Avalonia.Controls.UnitTests
keyboardNavigation: new KeyboardNavigationHandler(),
inputManager: new InputManager(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
textShaperImpl: new MockTextShaperImpl(),
fontManagerImpl: new MockFontManagerImpl());
textShaperImpl: new HeadlessTextShaperStub(),
fontManagerImpl: new HeadlessFontManagerStub());
private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<ICursorFactory>(),
renderInterface: new MockPlatformRenderInterface(),
textShaperImpl: new MockTextShaperImpl(),
fontManagerImpl: new MockFontManagerImpl());
renderInterface: new HeadlessPlatformRenderInterface(),
textShaperImpl: new HeadlessTextShaperStub(),
fontManagerImpl: new HeadlessFontManagerStub());
private IControlTemplate CreateTemplate()
{

5
tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs

@ -6,6 +6,7 @@ using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Headless;
using Avalonia.Markup.Data;
using Avalonia.Platform;
using Avalonia.UnitTests;
@ -87,8 +88,8 @@ namespace Avalonia.Controls.UnitTests
private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<ICursorFactory>(),
textShaperImpl: new MockTextShaperImpl(),
fontManagerImpl: new MockFontManagerImpl());
textShaperImpl: new HeadlessTextShaperStub(),
fontManagerImpl: new HeadlessFontManagerStub());
private static IControlTemplate CreateTemplate()
{

7
tests/Avalonia.Controls.UnitTests/TimePickerTests.cs

@ -2,6 +2,7 @@
using System.Linq;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Headless;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
@ -99,10 +100,10 @@ namespace Avalonia.Controls.UnitTests
}
private static TestServices Services => TestServices.MockThreadingInterface.With(
fontManagerImpl: new MockFontManagerImpl(),
fontManagerImpl: new HeadlessFontManagerStub(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
textShaperImpl: new MockTextShaperImpl(),
renderInterface: new MockPlatformRenderInterface());
textShaperImpl: new HeadlessTextShaperStub(),
renderInterface: new HeadlessPlatformRenderInterface());
private static IControlTemplate CreateTemplate()
{

7
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -9,6 +9,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Layout;
@ -1694,12 +1695,12 @@ namespace Avalonia.Controls.UnitTests
return UnitTestApplication.Start(
TestServices.MockThreadingInterface.With(
focusManager: new FocusManager(),
fontManagerImpl: new MockFontManagerImpl(),
fontManagerImpl: new HeadlessFontManagerStub(),
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: new KeyboardNavigationHandler(),
inputManager: new InputManager(),
renderInterface: new MockPlatformRenderInterface(),
textShaperImpl: new MockTextShaperImpl()));
renderInterface: new HeadlessPlatformRenderInterface(),
textShaperImpl: new HeadlessTextShaperStub()));
}
private class Node : NotifyingBase

2
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -402,6 +402,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
[Fact]
public void Style_Can_Use_NthChild_Selector_With_ItemsRepeater()
{
GC.KeepAlive(typeof(ItemsRepeater));
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"

3
tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Headless;
using Avalonia.Media;
using Avalonia.UnitTests;
using SkiaSharp;
@ -90,7 +91,7 @@ namespace Avalonia.Skia.UnitTests.Media
[Fact]
public void Should_Only_Try_To_Create_GlyphTypeface_Once()
{
var fontManagerImpl = new MockFontManagerImpl();
var fontManagerImpl = new HeadlessFontManagerStub();
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: fontManagerImpl)))
{

1
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Headless;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;

1
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Headless;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.UnitTests;

1
tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj

@ -10,6 +10,7 @@
<EmbeddedResource Include="..\Avalonia.UnitTests\Assets\*.ttf" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Headless\Avalonia.Headless\Avalonia.Headless.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />

62
tests/Avalonia.UnitTests/ImmediateDispatcher.cs

@ -1,62 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace Avalonia.UnitTests
{
/// <summary>
/// Immediately invokes dispatched jobs on the current thread.
/// </summary>
public class ImmediateDispatcher : IDispatcher
{
/// <inheritdoc/>
public bool CheckAccess()
{
return true;
}
/// <inheritdoc/>
public void Post(Action action, DispatcherPriority priority)
{
action();
}
/// <inheritdoc/>
public void Post(SendOrPostCallback action, object arg, DispatcherPriority priority)
{
action(arg);
}
/// <inheritdoc/>
public Task InvokeAsync(Action action, DispatcherPriority priority)
{
action();
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority)
{
var result = function();
return Task.FromResult(result);
}
/// <inheritdoc/>
public Task InvokeAsync(Func<Task> function, DispatcherPriority priority)
{
return function();
}
/// <inheritdoc/>
public Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> function, DispatcherPriority priority)
{
return function();
}
/// <inheritdoc/>
public void VerifyAccess()
{
}
}
}

63
tests/Avalonia.UnitTests/MockFontManagerImpl.cs

@ -1,63 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.UnitTests
{
public class MockFontManagerImpl : IFontManagerImpl
{
private readonly string _defaultFamilyName;
public MockFontManagerImpl(string defaultFamilyName = "Default")
{
_defaultFamilyName = defaultFamilyName;
}
public int TryCreateGlyphTypefaceCount { get; private set; }
public string GetDefaultFontFamilyName()
{
return _defaultFamilyName;
}
string[] IFontManagerImpl.GetInstalledFontFamilyNames(bool checkForUpdates)
{
return new[] { _defaultFamilyName };
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight,
FontStretch fontStretch,
CultureInfo culture, out Typeface fontKey)
{
fontKey = new Typeface(_defaultFamilyName);
return false;
}
public virtual bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight,
FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface)
{
glyphTypeface = null;
TryCreateGlyphTypefaceCount++;
if (familyName == "Unknown")
{
return false;
}
glyphTypeface = new MockGlyphTypeface();
return true;
}
public virtual bool TryCreateGlyphTypeface(Stream stream, out IGlyphTypeface glyphTypeface)
{
glyphTypeface = new MockGlyphTypeface();
return true;
}
}
}

34
tests/Avalonia.UnitTests/MockGlyphRun.cs

@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.UnitTests
{
public class MockGlyphRun : IGlyphRunImpl
{
public MockGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, Point baselineOrigin, Rect bounds)
{
GlyphTypeface = glyphTypeface;
FontRenderingEmSize = fontRenderingEmSize;
BaselineOrigin = baselineOrigin;
Bounds =bounds;
}
public IGlyphTypeface GlyphTypeface { get; }
public double FontRenderingEmSize { get; }
public Point BaselineOrigin { get; }
public Rect Bounds { get; }
public void Dispose()
{
}
public IReadOnlyList<float> GetIntersections(float lowerLimit, float upperLimit)
=> Array.Empty<float>();
}
}

81
tests/Avalonia.UnitTests/MockGlyphTypeface.cs

@ -1,81 +0,0 @@
using System;
using Avalonia.Media;
namespace Avalonia.UnitTests
{
public class MockGlyphTypeface : IGlyphTypeface
{
public FontMetrics Metrics => new FontMetrics
{
DesignEmHeight = 10,
Ascent = 2,
Descent = 10,
IsFixedPitch = true
};
public int GlyphCount => 1337;
public FontSimulations FontSimulations => throw new NotImplementedException();
public string FamilyName => "$Default";
public FontWeight Weight { get; }
public FontStyle Style { get; }
public FontStretch Stretch { get; }
public ushort GetGlyph(uint codepoint)
{
return (ushort)codepoint;
}
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints)
{
return new ushort[codepoints.Length];
}
public int GetGlyphAdvance(ushort glyph)
{
return 8;
}
public bool TryGetGlyph(uint codepoint, out ushort glyph)
{
glyph = 8;
return true;
}
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{
var advances = new int[glyphs.Length];
for (var i = 0; i < advances.Length; i++)
{
advances[i] = 8;
}
return advances;
}
public void Dispose() { }
public bool TryGetTable(uint tag, out byte[] table)
{
table = null;
return false;
}
public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics)
{
metrics = new GlyphMetrics
{
Width = 10,
Height = 10
};
return true;
}
}
}

174
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -1,174 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Media.Imaging;
using Avalonia.Media.TextFormatting;
using Moq;
namespace Avalonia.UnitTests
{
public class MockPlatformRenderInterface : IPlatformRenderInterface, IPlatformRenderInterfaceContext
{
public IGeometryImpl CreateEllipseGeometry(Rect rect)
{
return Mock.Of<IGeometryImpl>();
}
public IGeometryImpl CreateLineGeometry(Point p1, Point p2)
{
return Mock.Of<IGeometryImpl>();
}
public IGeometryImpl CreateRectangleGeometry(Rect rect)
{
return Mock.Of<IGeometryImpl>(x => x.Bounds == rect);
}
class MockRenderTarget : IRenderTarget
{
public void Dispose()
{
}
public IDrawingContextImpl CreateDrawingContext()
{
var m = new Mock<IDrawingContextImpl>();
m.Setup(c => c.CreateLayer(It.IsAny<Size>()))
.Returns(() =>
{
var r = new Mock<IDrawingContextLayerImpl>();
r.Setup(r => r.CreateDrawingContext())
.Returns(CreateDrawingContext());
return r.Object;
}
);
return m.Object;
}
public bool IsCorrupted => false;
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
return new MockRenderTarget();
}
public bool IsLost => false;
public object TryGetFeature(Type featureType) => null;
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
{
return Mock.Of<IRenderTargetBitmapImpl>();
}
public IStreamGeometryImpl CreateStreamGeometry()
{
return new MockStreamGeometryImpl();
}
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<IGeometryImpl> children)
{
return Mock.Of<IGeometryImpl>();
}
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, IGeometryImpl g1, IGeometryImpl g2)
{
return Mock.Of<IGeometryImpl>();
}
public IWriteableBitmapImpl CreateWriteableBitmap(
PixelSize size,
Vector dpi,
PixelFormat format,
AlphaFormat alphaFormat)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(Stream stream)
{
return Mock.Of<IBitmapImpl>();
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(string fileName)
{
return Mock.Of<IBitmapImpl>();
}
public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return Mock.Of<IBitmapImpl>();
}
public IBitmapImpl LoadBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return Mock.Of<IBitmapImpl>();
}
public IBitmapImpl ResizeBitmap(IBitmapImpl bitmapImpl, PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return Mock.Of<IBitmapImpl>();
}
public IBitmapImpl LoadBitmap(
PixelFormat format,
AlphaFormat alphaFormat,
IntPtr data,
PixelSize size,
Vector dpi,
int stride)
{
throw new NotImplementedException();
}
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{
return new MockGlyphRun(glyphTypeface, fontRenderingEmSize, baselineOrigin, bounds);
}
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => this;
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
return Mock.Of<IGeometryImpl>();
}
public bool SupportsIndividualRoundRects { get; set; }
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
public void Dispose()
{
}
}
}

179
tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs

@ -1,179 +0,0 @@
using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.UnitTests
{
public class MockStreamGeometryImpl : IStreamGeometryImpl, ITransformedGeometryImpl
{
private MockStreamGeometryContext _context;
public MockStreamGeometryImpl()
{
Transform = Matrix.Identity;
_context = new MockStreamGeometryContext();
}
public MockStreamGeometryImpl(Matrix transform)
{
Transform = transform;
_context = new MockStreamGeometryContext();
}
private MockStreamGeometryImpl(Matrix transform, MockStreamGeometryContext context)
{
Transform = transform;
_context = context;
}
public IGeometryImpl SourceGeometry { get; }
public Rect Bounds => _context.CalculateBounds();
public double ContourLength { get; }
public Matrix Transform { get; }
public IStreamGeometryImpl Clone()
{
return this;
}
public void Dispose()
{
}
public bool FillContains(Point point)
{
return _context.FillContains(point);
}
public bool StrokeContains(IPen pen, Point point)
{
return false;
}
public Rect GetRenderBounds(IPen pen) => Bounds;
public IGeometryImpl Intersect(IGeometryImpl geometry)
{
return new MockStreamGeometryImpl(Transform);
}
public IStreamGeometryContextImpl Open()
{
return _context;
}
public ITransformedGeometryImpl WithTransform(Matrix transform)
{
return new MockStreamGeometryImpl(transform, _context);
}
public bool TryGetPointAtDistance(double distance, out Point point)
{
point = new Point();
return false;
}
public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent)
{
point = new Point();
tangent = new Point();
return false;
}
public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry)
{
segmentGeometry = null;
return false;
}
class MockStreamGeometryContext : IStreamGeometryContextImpl
{
private List<Point> points = new List<Point>();
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
}
public void BeginFigure(Point startPoint, bool isFilled)
{
points.Add(startPoint);
}
public Rect CalculateBounds()
{
var left = double.MaxValue;
var right = double.MinValue;
var top = double.MaxValue;
var bottom = double.MinValue;
foreach (var p in points)
{
left = Math.Min(p.X, left);
right = Math.Max(p.X, right);
top = Math.Min(p.Y, top);
bottom = Math.Max(p.Y, bottom);
}
return new Rect(new Point(left, top), new Point(right, bottom));
}
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
}
public void Dispose()
{
}
public void EndFigure(bool isClosed)
{
}
public void LineTo(Point point)
{
points.Add(point);
}
public void QuadraticBezierTo(Point control, Point endPoint)
{
throw new NotImplementedException();
}
public void SetFillRule(FillRule fillRule)
{
}
public bool FillContains(Point point)
{
// Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++)
{
var a = points[i];
var b = points[(i + 1) % points.Count];
var c = points[(i + 2) % points.Count];
Vector v0 = c - a;
Vector v1 = b - a;
Vector v2 = point - a;
var dot00 = v0 * v0;
var dot01 = v0 * v1;
var dot02 = v0 * v2;
var dot11 = v1 * v1;
var dot12 = v1 * v2;
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
if ((u >= 0) && (v >= 0) && (u + v < 1)) return true;
}
return false;
}
}
}
}

38
tests/Avalonia.UnitTests/MockTextShaperImpl.cs

@ -1,38 +0,0 @@
using System;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
namespace Avalonia.UnitTests
{
public class MockTextShaperImpl : ITextShaperImpl
{
public ShapedBuffer ShapeText(ReadOnlyMemory<char> text, TextShaperOptions options)
{
var typeface = options.Typeface;
var fontRenderingEmSize = options.FontRenderingEmSize;
var bidiLevel = options.BidiLevel;
var shapedBuffer = new ShapedBuffer(text, text.Length, typeface, fontRenderingEmSize, bidiLevel);
var textSpan = text.Span;
var textStartIndex = TextTestHelper.GetStartCharIndex(text);
for (var i = 0; i < shapedBuffer.Length;)
{
var glyphCluster = i + textStartIndex;
var codepoint = Codepoint.ReadAt(textSpan, i, out var count);
var glyphIndex = typeface.GetGlyph(codepoint);
for (var j = 0; j < count; ++j)
{
shapedBuffer[i + j] = new GlyphInfo(glyphIndex, glyphCluster, 10);
}
i += count;
}
return shapedBuffer;
}
}
}

34
tests/Avalonia.UnitTests/TestServices.cs

@ -13,6 +13,7 @@ using System.Collections.Generic;
using Avalonia.Controls;
using System.Reflection;
using Avalonia.Animation;
using Avalonia.Headless;
using Avalonia.Threading;
namespace Avalonia.UnitTests
@ -22,25 +23,25 @@ namespace Avalonia.UnitTests
public static readonly TestServices StyledWindow = new TestServices(
assetLoader: new StandardAssetLoader(),
platform: new StandardRuntimePlatform(),
renderInterface: new MockPlatformRenderInterface(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
renderInterface: new HeadlessPlatformRenderInterface(),
standardCursorFactory: new HeadlessCursorFactoryStub(),
theme: () => CreateSimpleTheme(),
dispatcherImpl: Mock.Of<IDispatcherImpl>(x => x.CurrentThreadIsLoopThread == true),
fontManagerImpl: new MockFontManagerImpl(),
textShaperImpl: new MockTextShaperImpl(),
dispatcherImpl: new NullDispatcherImpl(),
fontManagerImpl: new HeadlessFontManagerStub(),
textShaperImpl: new HeadlessTextShaperStub(),
windowingPlatform: new MockWindowingPlatform());
public static readonly TestServices MockPlatformRenderInterface = new TestServices(
assetLoader: new StandardAssetLoader(),
renderInterface: new MockPlatformRenderInterface(),
fontManagerImpl: new MockFontManagerImpl(),
textShaperImpl: new MockTextShaperImpl());
renderInterface: new HeadlessPlatformRenderInterface(),
fontManagerImpl: new HeadlessFontManagerStub(),
textShaperImpl: new HeadlessTextShaperStub());
public static readonly TestServices MockPlatformWrapper = new TestServices(
platform: Mock.Of<IRuntimePlatform>());
public static readonly TestServices MockThreadingInterface = new TestServices(
dispatcherImpl: Mock.Of<IDispatcherImpl>(x => x.CurrentThreadIsLoopThread == true));
dispatcherImpl: new NullDispatcherImpl());
public static readonly TestServices MockWindowingPlatform = new TestServices(
windowingPlatform: new MockWindowingPlatform());
@ -51,13 +52,13 @@ namespace Avalonia.UnitTests
keyboardNavigation: new KeyboardNavigationHandler(),
inputManager: new InputManager(),
assetLoader: new StandardAssetLoader(),
renderInterface: new MockPlatformRenderInterface(),
fontManagerImpl: new MockFontManagerImpl(),
textShaperImpl: new MockTextShaperImpl());
renderInterface: new HeadlessPlatformRenderInterface(),
fontManagerImpl: new HeadlessFontManagerStub(),
textShaperImpl: new HeadlessTextShaperStub());
public static readonly TestServices TextServices = new TestServices(
assetLoader: new StandardAssetLoader(),
renderInterface: new MockPlatformRenderInterface(),
renderInterface: new HeadlessPlatformRenderInterface(),
fontManagerImpl: new HarfBuzzFontManagerImpl(),
textShaperImpl: new HarfBuzzTextShaperImpl());
@ -158,12 +159,5 @@ namespace Avalonia.UnitTests
{
return new SimpleTheme();
}
private static IPlatformRenderInterface CreateRenderInterfaceMock()
{
return Mock.Of<IPlatformRenderInterface>(x =>
x.CreateStreamGeometry() == Mock.Of<IStreamGeometryImpl>(
y => y.Open() == Mock.Of<IStreamGeometryContextImpl>()));
}
}
}

15
tests/Avalonia.UnitTests/TextTestHelper.cs

@ -1,15 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.UnitTests
{
public static class TextTestHelper
{
public static int GetStartCharIndex(ReadOnlyMemory<char> text)
{
if (!MemoryMarshal.TryGetString(text, out _, out var start, out _))
throw new InvalidOperationException("text memory should have been a string");
return start;
}
}
}
Loading…
Cancel
Save