using System.Collections.Generic; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering.Composition.Drawing; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; using Moq; using Xunit; namespace Avalonia.Base.UnitTests.Rendering.SceneGraph { public class RenderDataStreamTests { [Fact] public void Replay_Forwards_Line() { var pen = Mock.Of(); var context = new Mock(); using var stream = new RenderDataStream(); stream.DrawLine(pen, pen, new Point(1, 2), new Point(3, 4)); stream.Replay(context.Object); context.Verify(x => x.DrawLine(pen, new Point(1, 2), new Point(3, 4)), Times.Once); } [Fact] public void Replay_Forwards_Rectangle_With_Box_Shadows() { var brush = Mock.Of(); var pen = Mock.Of(); var rect = new RoundedRect(new Rect(0, 0, 10, 20)); var shadows = new BoxShadows( new BoxShadow { Blur = 1 }, new[] { new BoxShadow { Blur = 2 } }); var context = new Mock(); using var stream = new RenderDataStream(); stream.DrawRectangle(brush, pen, pen, rect, shadows); stream.Replay(context.Object); context.Verify(x => x.DrawRectangle(brush, pen, rect, It.Is(s => s.Count == 2)), Times.Once); } [Fact] public void Replay_Forwards_Custom_Operation() { var operation = new Mock(); var context = new Mock(); using var stream = new RenderDataStream(); stream.DrawCustom(operation.Object); stream.Replay(context.Object); operation.Verify(x => x.Render(It.IsAny()), Times.Once); } [Fact] public void Replay_Pop_Dispatches_To_Matching_Pop_In_Lifo_Order() { var calls = new List(); var context = RecordingContext(calls); using var stream = new RenderDataStream(); stream.PushClip(new RoundedRect(new Rect(0, 0, 10, 10))); stream.PushOpacity(0.5); stream.Pop(); stream.Pop(); stream.Replay(context.Object); Assert.Equal(new[] { "PushClip", "PushOpacity", "PopOpacity", "PopClip" }, calls); } [Fact] public void Replay_Applies_And_Restores_Transform() { var context = new Mock(); context.SetupProperty(x => x.Transform); context.Object.Transform = Matrix.Identity; var matrix = Matrix.CreateTranslation(5, 7); using var stream = new RenderDataStream(); stream.PushTransform(matrix); stream.Pop(); stream.Replay(context.Object); Assert.Equal(Matrix.Identity, context.Object.Transform); } [Fact] public void Replay_Skips_Opacity_One_Push() { var calls = new List(); var context = RecordingContext(calls); using var stream = new RenderDataStream(); stream.PushOpacity(1); stream.Pop(); stream.Replay(context.Object); Assert.Empty(calls); } [Fact] public void Replay_Skips_Null_Geometry_Clip() { var calls = new List(); var context = RecordingContext(calls); using var stream = new RenderDataStream(); stream.PushGeometryClip(null); stream.Pop(); stream.Replay(context.Object); Assert.Empty(calls); } [Fact] public void Replay_Walks_Nested_Pushes_In_Order() { var calls = new List(); var context = RecordingContext(calls); var geometry = new Mock(); geometry.Setup(g => g.GeometryImpl).Returns(geometry.Object); using var stream = new RenderDataStream(); stream.PushClip(new RoundedRect(new Rect(0, 0, 10, 10))); stream.PushGeometryClip(geometry.Object); stream.Pop(); stream.Pop(); stream.Replay(context.Object); Assert.Equal( new[] { "PushClip", "PushGeometryClip", "PopGeometryClip", "PopClip" }, calls); } [Fact] public void Replay_Forwards_Render_Options() { var options = new RenderOptions { EdgeMode = EdgeMode.Aliased }; var context = new Mock(); using var stream = new RenderDataStream(); stream.PushRenderOptions(options); stream.Pop(); stream.Replay(context.Object); context.Verify(x => x.PushRenderOptions(options), Times.Once); context.Verify(x => x.PopRenderOptions(), Times.Once); } [Fact] public void Replay_Forwards_Text_Options() { var options = new TextOptions { TextRenderingMode = TextRenderingMode.Antialias }; var context = new Mock(); using var stream = new RenderDataStream(); stream.PushTextOptions(options); stream.Pop(); stream.Replay(context.Object); context.Verify(x => x.PushTextOptions(options), Times.Once); context.Verify(x => x.PopTextOptions(), Times.Once); } [Fact] public void Dispose_Resources_Disposes_Owned_Resources() { var bitmap = RefCountable.Create(Mock.Of()); var glyphRun = RefCountable.Create(Mock.Of()); var operation = new Mock(); using (var stream = new RenderDataStream()) { stream.DrawBitmap(bitmap.Clone(), 1, new Rect(0, 0, 1, 1), new Rect(0, 0, 1, 1)); stream.DrawGlyphRun(null, glyphRun.Clone()); stream.DrawCustom(operation.Object); Assert.Equal(2, bitmap.RefCount); Assert.Equal(2, glyphRun.RefCount); stream.DisposeResources(); } Assert.Equal(1, bitmap.RefCount); Assert.Equal(1, glyphRun.RefCount); operation.Verify(x => x.Dispose(), Times.Once); bitmap.Dispose(); glyphRun.Dispose(); } [Fact] public void Replay_Handles_Deeply_Nested_Scopes() { var context = new Mock(); using var stream = new RenderDataStream(); for (var i = 0; i < 100; i++) stream.PushOpacity(0.5); stream.DrawRectangle(Brushes.Black, null, null, new RoundedRect(new Rect(0, 0, 10, 10)), default); for (var i = 0; i < 100; i++) stream.Pop(); stream.Replay(context.Object); context.Verify(x => x.DrawRectangle(Brushes.Black, null, It.IsAny(), It.IsAny()), Times.Once); } private static Mock RecordingContext(List calls) { var context = new Mock(); context.Setup(x => x.PushClip(It.IsAny())).Callback(() => calls.Add("PushClip")); context.Setup(x => x.PopClip()).Callback(() => calls.Add("PopClip")); context.Setup(x => x.PushGeometryClip(It.IsAny())) .Callback(() => calls.Add("PushGeometryClip")); context.Setup(x => x.PopGeometryClip()).Callback(() => calls.Add("PopGeometryClip")); context.Setup(x => x.PushOpacity(It.IsAny(), It.IsAny())) .Callback(() => calls.Add("PushOpacity")); context.Setup(x => x.PopOpacity()).Callback(() => calls.Add("PopOpacity")); return context; } } }