Browse Source

Reuse text and geometry related headless mocks in Avalonia unit tests and benchmarks

Max Katz 2 years ago
parent
commit
4aa6035ec3
41 changed files with 248 additions and 1386 deletions
  1. 9 0
      src/Headless/Avalonia.Headless/Avalonia.Headless.csproj
  2. 80 15
      src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  3. 85 24
      src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs
  4. 4 3
      tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs
  5. 3 2
      tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
  6. 1 0
      tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs
  7. 0 285
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  8. 2 5
      tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs
  9. 0 17
      tests/Avalonia.Benchmarks/NullCursorFactory.cs
  10. 0 106
      tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs
  11. 0 21
      tests/Avalonia.Benchmarks/NullGlyphRun.cs
  12. 0 149
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  13. 0 27
      tests/Avalonia.Benchmarks/NullThreadingPlatform.cs
  14. 3 2
      tests/Avalonia.Benchmarks/Rendering/ShapeRendering.cs
  15. 1 4
      tests/Avalonia.Benchmarks/Styling/ControlTheme_Change.cs
  16. 1 4
      tests/Avalonia.Benchmarks/Styling/ResourceBenchmarks.cs
  17. 1 4
      tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs
  18. 1 4
      tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs
  19. 0 3
      tests/Avalonia.Benchmarks/Themes/FluentBenchmark.cs
  20. 4 3
      tests/Avalonia.Controls.UnitTests/DatePickerTests.cs
  21. 4 3
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  22. 7 6
      tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
  23. 4 3
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  24. 6 5
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  25. 3 2
      tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs
  26. 4 3
      tests/Avalonia.Controls.UnitTests/TimePickerTests.cs
  27. 4 3
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  28. 2 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  29. 2 1
      tests/Avalonia.Skia.UnitTests/Media/FontManagerTests.cs
  30. 1 0
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  31. 1 0
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
  32. 1 0
      tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj
  33. 0 62
      tests/Avalonia.UnitTests/ImmediateDispatcher.cs
  34. 0 63
      tests/Avalonia.UnitTests/MockFontManagerImpl.cs
  35. 0 33
      tests/Avalonia.UnitTests/MockGlyphRun.cs
  36. 0 81
      tests/Avalonia.UnitTests/MockGlyphTypeface.cs
  37. 0 191
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  38. 0 179
      tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
  39. 0 38
      tests/Avalonia.UnitTests/MockTextShaperImpl.cs
  40. 14 20
      tests/Avalonia.UnitTests/TestServices.cs
  41. 0 15
      tests/Avalonia.UnitTests/TextTestHelper.cs

+ 9 - 0
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>

+ 80 - 15
src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@@ -130,12 +130,29 @@ namespace Avalonia.Headless
             IReadOnlyList<GlyphInfo> glyphInfos, 
             Point baselineOrigin)
         {
-            return new HeadlessGlyphRunStub();
+            return new HeadlessGlyphRunStub(glyphInfos);
         }
 
-        private class HeadlessGlyphRunStub : IGlyphRunImpl
+        internal class HeadlessGlyphRunStub : IGlyphRunImpl
         {
-            public Rect Bounds => new Rect(new Size(8, 12));
+            public HeadlessGlyphRunStub(IReadOnlyList<GlyphInfo> glyphInfos)
+            {
+                var width = 0.0;
+
+                for (var i = 0; i < glyphInfos.Count; ++i)
+                {
+                    width += glyphInfos[i].GlyphAdvance;
+                }
+
+                Bounds = new Rect(new Size(width, 10));
+            }
+            
+            public HeadlessGlyphRunStub()
+            {
+                Bounds = new Rect(new Size(8, 10));
+            }
+
+            public Rect Bounds { get; }
 
             public Point BaselineOrigin => new Point(0, 8);
 
@@ -232,8 +249,11 @@ namespace Avalonia.Headless
 
         private class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl
         {
+            private HeadlessStreamingGeometryContextStub _context;
+            
             public HeadlessStreamingGeometryStub() : base(default)
             {
+                _context = new HeadlessStreamingGeometryContextStub(this);
             }
 
             public IStreamGeometryImpl Clone()
@@ -243,13 +263,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;
@@ -257,19 +282,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)
@@ -301,6 +337,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;
+                }
             }
         }
 
@@ -366,7 +431,7 @@ namespace Avalonia.Headless
             }
         }
 
-        private class HeadlessDrawingContextStub : IDrawingContextImpl
+        internal class HeadlessDrawingContextStub : IDrawingContextImpl
         {
             public void Dispose()
             {

+ 85 - 24
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;
+        }
+    }
 }

+ 4 - 3
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);
 

+ 3 - 2
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 - 0
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;

+ 0 - 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)
-        {
-            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()
-        {
-            
-        }
-    }
-
-}

+ 2 - 5
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)
             {

+ 0 - 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() { }
-        }
-    }
-}

+ 0 - 106
tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs

@@ -1,106 +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 void Clear(Color color)
-        {
-        }
-
-        public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect,
-            BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
-        {
-        }
-
-        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;
-    }
-}

+ 0 - 21
tests/Avalonia.Benchmarks/NullGlyphRun.cs

@@ -1,21 +0,0 @@
-using System.Collections.Generic;
-using Avalonia.Platform;
-
-namespace Avalonia.Benchmarks
-{
-    internal class NullGlyphRun : IGlyphRunImpl
-    {
-        public Rect Bounds => default;
-
-        public Point BaselineOrigin => default;
-
-        public void Dispose()
-        {
-        }
-
-        public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
-        {
-            return null;
-        }
-    }
-}

+ 0 - 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)
-        {
-            return new MockGlyphRun(glyphInfos);
-        }
-
-        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()
-        {
-            
-        }
-    }
-}

+ 0 - 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
-    }
-}

+ 3 - 2
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]

+ 1 - 4
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.

+ 1 - 4
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 AssetLoader(),
                 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);

+ 1 - 4
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.

+ 1 - 4
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)
         {

+ 0 - 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);

+ 4 - 3
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()
         {

+ 4 - 3
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

+ 7 - 6
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;
@@ -892,16 +893,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()
         {

+ 4 - 3
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;
@@ -1339,12 +1340,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

+ 6 - 5
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;
@@ -1104,14 +1105,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()
         {

+ 3 - 2
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()
         {

+ 4 - 3
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()
         {

+ 4 - 3
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 - 0
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 = @"

+ 2 - 1
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 - 0
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 - 0
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 - 0
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" />

+ 0 - 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()
-        {
-        }
-    }
-}

+ 0 - 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;
-        }
-    }
-}

+ 0 - 33
tests/Avalonia.UnitTests/MockGlyphRun.cs

@@ -1,33 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Media.TextFormatting;
-using Avalonia.Platform;
-
-namespace Avalonia.UnitTests
-{
-    public class MockGlyphRun : IGlyphRunImpl
-    {
-        public MockGlyphRun(IReadOnlyList<GlyphInfo> glyphInfos)
-        {
-            var width = 0.0;
-
-            for (var i = 0; i < glyphInfos.Count; ++i)
-            {
-                width += glyphInfos[i].GlyphAdvance;
-            }
-
-            Bounds = new Rect(new Size(width, 10));
-        }
-
-        public Rect Bounds { get; }
-
-        public Point BaselineOrigin => new Point(0, 8);
-
-        public void Dispose()
-        {
-        }
-
-        public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
-            => Array.Empty<float>();
-    }
-}

+ 0 - 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;
-        }
-    }
-}

+ 0 - 191
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@@ -1,191 +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 Avalonia.Rendering;
-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)
-        {
-            return new MockGlyphRun(glyphInfos);
-        }
-
-        public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => this;
-
-        public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
-        {
-            return Mock.Of<IGeometryImpl>();
-        }
-
-        public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
-        {
-            return Mock.Of<IGlyphRunBuffer>();
-        }
-
-        public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
-        {
-            return Mock.Of<IHorizontalGlyphRunBuffer>();
-        }
-
-        public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
-        {
-            return Mock.Of<IPositionedGlyphRunBuffer>();
-        }
-
-        public bool SupportsIndividualRoundRects { get; set; }
-
-        public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
-
-        public PixelFormat DefaultPixelFormat => PixelFormat.Rgba8888;
-        public bool IsSupportedBitmapPixelFormat(PixelFormat format) => true;
-
-        public void Dispose()
-        {
-        }
-    }
-}

+ 0 - 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;
-            }
-        }
-    }
-}

+ 0 - 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;
-        }
-    }
-}

+ 14 - 20
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 AssetLoader(),
             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 AssetLoader(),
-            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 AssetLoader(),
-            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 AssetLoader(),
-            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>()));
-        }
     }
 }

+ 0 - 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;
-        }
-    }
-}