Browse Source

Enhance layout testing engine

Marcin Ziąbek 2 months ago
parent
commit
a7e9c5bded

+ 1 - 0
Source/QuestPDF.LayoutTests/TestEngine/DrawingRecorder.cs

@@ -8,6 +8,7 @@ internal class ElementDrawingEvent
     public int PageNumber { get; set; }
     public int PageNumber { get; set; }
     public Position Position { get; set; }
     public Position Position { get; set; }
     public Size Size { get; set; }
     public Size Size { get; set; }
+    public object? StateAfterDrawing { get; set; }
 }
 }
 
 
 internal class DrawingRecorder
 internal class DrawingRecorder

+ 7 - 3
Source/QuestPDF.LayoutTests/TestEngine/ElementObserver.cs

@@ -15,14 +15,16 @@ internal class ElementObserver : ContainerElement
         Debug.Assert(DrawingRecorder != null);
         Debug.Assert(DrawingRecorder != null);
         
         
         var matrix = Canvas.GetCurrentMatrix();
         var matrix = Canvas.GetCurrentMatrix();
-        
-        DrawingRecorder?.Record(new ElementDrawingEvent
+
+        var drawingEvent = new ElementDrawingEvent
         {
         {
             ObserverId = ObserverId,
             ObserverId = ObserverId,
             PageNumber = PageContext.CurrentPage,
             PageNumber = PageContext.CurrentPage,
             Position = new Position(matrix.TranslateX, matrix.TranslateY),
             Position = new Position(matrix.TranslateX, matrix.TranslateY),
             Size = ObserverId == "$document" ? Child.Measure(availableSpace) : availableSpace
             Size = ObserverId == "$document" ? Child.Measure(availableSpace) : availableSpace
-        });
+        };
+        
+        DrawingRecorder?.Record(drawingEvent);
         
         
         var matrixBeforeDraw = Canvas.GetCurrentMatrix().ToMatrix4x4();
         var matrixBeforeDraw = Canvas.GetCurrentMatrix().ToMatrix4x4();
         base.Draw(availableSpace);
         base.Draw(availableSpace);
@@ -30,5 +32,7 @@ internal class ElementObserver : ContainerElement
         
         
         if (matrixAfterDraw != matrixBeforeDraw)
         if (matrixAfterDraw != matrixBeforeDraw)
             throw new InvalidOperationException("Canvas state was not restored after drawing operation.");
             throw new InvalidOperationException("Canvas state was not restored after drawing operation.");
+
+        drawingEvent.StateAfterDrawing = (Child as IStateful)?.GetState();
     }
     }
 }
 }

+ 6 - 0
Source/QuestPDF.LayoutTests/TestEngine/FluentExtensions.cs

@@ -61,6 +61,12 @@ internal class ExpectedMockPositionDescriptor(ElementDrawingEvent drawingEvent)
         drawingEvent.Size = new Size(width, height);
         drawingEvent.Size = new Size(width, height);
         return this;
         return this;
     }
     }
+    
+    public ExpectedMockPositionDescriptor State(object state)
+    {
+        drawingEvent.StateAfterDrawing = state;
+        return this;
+    }
 }
 }
 
 
 internal static class FluentExtensions
 internal static class FluentExtensions

+ 7 - 1
Source/QuestPDF.LayoutTests/TestEngine/LayoutTest.cs

@@ -1,5 +1,6 @@
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Text;
 using System.Text;
+using NUnit.Framework.Constraints;
 using QuestPDF.Drawing.Exceptions;
 using QuestPDF.Drawing.Exceptions;
 using QuestPDF.Elements;
 using QuestPDF.Elements;
 using QuestPDF.Helpers;
 using QuestPDF.Helpers;
@@ -14,6 +15,8 @@ internal class LayoutTest
     private DrawingRecorder ExpectedDrawingRecorder { get; } = new();
     private DrawingRecorder ExpectedDrawingRecorder { get; } = new();
     private IContainer? Content { get; set; }
     private IContainer? Content { get; set; }
   
   
+    private static readonly NUnitEqualityComparer Comparer = new();
+
     public static LayoutTest HavingSpaceOfSize(float width, float height, [CallerMemberName] string testIdentifier = "test")
     public static LayoutTest HavingSpaceOfSize(float width, float height, [CallerMemberName] string testIdentifier = "test")
     {
     {
         var layoutTest = new LayoutTest
         var layoutTest = new LayoutTest
@@ -79,10 +82,13 @@ internal class LayoutTest
             if (actual == null || expected == null)
             if (actual == null || expected == null)
                 return false;
                 return false;
             
             
+            var tolerance = Tolerance.Default;
+            
             return actual.ObserverId == expected.ObserverId &&
             return actual.ObserverId == expected.ObserverId &&
                    actual.PageNumber == expected.PageNumber &&
                    actual.PageNumber == expected.PageNumber &&
                    Position.Equal(actual.Position, expected.Position) &&
                    Position.Equal(actual.Position, expected.Position) &&
-                   Size.Equal(actual.Size, expected.Size);
+                   Size.Equal(actual.Size, expected.Size) &&
+                   (expected.StateAfterDrawing == null || Comparer.AreEqual(actual.StateAfterDrawing, expected.StateAfterDrawing, ref tolerance));
         }
         }
 
 
         static void DrawLog(IReadOnlyCollection<ElementDrawingEvent> actualEvents, IReadOnlyCollection<ElementDrawingEvent> expectedEvents)
         static void DrawLog(IReadOnlyCollection<ElementDrawingEvent> actualEvents, IReadOnlyCollection<ElementDrawingEvent> expectedEvents)

+ 4 - 3
Source/QuestPDF/Elements/Line.cs

@@ -35,7 +35,7 @@ namespace QuestPDF.Elements
 
 
             if (Type == LineType.Vertical)
             if (Type == LineType.Vertical)
             {
             {
-                if (availableSpace.Width + Size.Epsilon < Thickness)
+                if (Thickness.IsGreaterThan(availableSpace.Width))
                     return SpacePlan.Wrap("The line thickness is greater than the available horizontal space.");
                     return SpacePlan.Wrap("The line thickness is greater than the available horizontal space.");
 
 
                 return SpacePlan.FullRender(Thickness, 0);
                 return SpacePlan.FullRender(Thickness, 0);
@@ -43,12 +43,13 @@ namespace QuestPDF.Elements
             
             
             if (Type == LineType.Horizontal)
             if (Type == LineType.Horizontal)
             {
             {
-                if (availableSpace.Height + Size.Epsilon < Thickness)
+                if (Thickness.IsGreaterThan(availableSpace.Height))
                     return SpacePlan.Wrap("The line thickness is greater than the available vertical space.");
                     return SpacePlan.Wrap("The line thickness is greater than the available vertical space.");
 
 
                 return SpacePlan.FullRender(0, Thickness);
                 return SpacePlan.FullRender(0, Thickness);
             }
             }
 
 
+            // Stryker disable once: unreachable code
             throw new NotSupportedException();
             throw new NotSupportedException();
         }
         }
 
 
@@ -96,7 +97,7 @@ namespace QuestPDF.Elements
         
         
         internal override string? GetCompanionHint()
         internal override string? GetCompanionHint()
         {
         {
-            return $"{Type} {Thickness:F1} {Color.ToString()}";
+            return $"{Type} {Thickness:0.#}";
         }
         }
     }
     }
 }
 }

+ 10 - 12
Source/QuestPDF/Elements/Row.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using QuestPDF.Drawing;
 using QuestPDF.Drawing;
+using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
 using QuestPDF.Infrastructure;
 
 
 namespace QuestPDF.Elements
 namespace QuestPDF.Elements
@@ -21,12 +22,12 @@ namespace QuestPDF.Elements
         public RowItemType Type { get; set; }
         public RowItemType Type { get; set; }
         public float Size { get; set; }
         public float Size { get; set; }
 
 
-        public override string ToString()
+        internal override string? GetCompanionHint()
         {
         {
             if (Type == RowItemType.Auto)
             if (Type == RowItemType.Auto)
                 return "Auto";
                 return "Auto";
             
             
-            return $"{Type}({Size})";
+            return $"{Type} {Size:0.#}";
         }
         }
     }
     }
 
 
@@ -47,12 +48,7 @@ namespace QuestPDF.Elements
         
         
         internal override IEnumerable<Element?> GetChildren()
         internal override IEnumerable<Element?> GetChildren()
         {
         {
-            return Items.Select(x => x.Child);
-        }
-        
-        internal override void CreateProxy(Func<Element?, Element?> create)
-        {
-            Items.ForEach(x => x.Child = create(x.Child));
+            return Items;
         }
         }
 
 
         internal override SpacePlan Measure(Size availableSpace)
         internal override SpacePlan Measure(Size availableSpace)
@@ -65,7 +61,7 @@ namespace QuestPDF.Elements
             
             
             UpdateItemsWidth(availableSpace.Width);
             UpdateItemsWidth(availableSpace.Width);
             
             
-            if (Items.Any(x => x.Width < -Size.Epsilon))
+            if (Items.Any(x => x.Width.IsLessThan(0)))
                 return SpacePlan.Wrap("One of the items has a negative size, indicating insufficient horizontal space. Usually, constant items require more space than is available, potentially causing other content to overflow.");
                 return SpacePlan.Wrap("One of the items has a negative size, indicating insufficient horizontal space. Usually, constant items require more space than is available, potentially causing other content to overflow.");
             
             
             var renderingCommands = PlanLayout(availableSpace);
             var renderingCommands = PlanLayout(availableSpace);
@@ -77,10 +73,10 @@ namespace QuestPDF.Elements
             var height = renderingCommands.Max(x => x.Size.Height);
             var height = renderingCommands.Max(x => x.Size.Height);
             var size = new Size(width, height);
             var size = new Size(width, height);
 
 
-            if (width > availableSpace.Width + Size.Epsilon)
+            if (width.IsGreaterThan(availableSpace.Width))
                 return SpacePlan.Wrap("The content requires more horizontal space than available.");
                 return SpacePlan.Wrap("The content requires more horizontal space than available.");
             
             
-            if (height > availableSpace.Height + Size.Epsilon)
+            if (height.IsGreaterThan(availableSpace.Height))
                 return SpacePlan.Wrap("The content requires more vertical space than available.");
                 return SpacePlan.Wrap("The content requires more vertical space than available.");
             
             
             if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.PartialRender))
             if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.PartialRender))
@@ -105,6 +101,7 @@ namespace QuestPDF.Elements
                 if (command.Measurement.Type is SpacePlanType.Empty or SpacePlanType.FullRender)
                 if (command.Measurement.Type is SpacePlanType.Empty or SpacePlanType.FullRender)
                     command.RowItem.IsRendered = true;
                     command.RowItem.IsRendered = true;
                 
                 
+                // TODO: investigate, as the final targetSize is still changed to use available vertical space
                 if (command.Measurement.Type is SpacePlanType.Wrap)
                 if (command.Measurement.Type is SpacePlanType.Wrap)
                     continue;
                     continue;
 
 
@@ -114,7 +111,7 @@ namespace QuestPDF.Elements
 
 
                 var targetSize = new Size(command.Size.Width, availableSpace.Height);
                 var targetSize = new Size(command.Size.Width, availableSpace.Height);
                     
                     
-                if (targetSize.Width < -Size.Epsilon)
+                if (targetSize.Width.IsLessThan(0))
                     continue;
                     continue;
                 
                 
                 Canvas.Translate(offset);
                 Canvas.Translate(offset);
@@ -165,6 +162,7 @@ namespace QuestPDF.Elements
                 leftOffset += item.Width + Spacing;
                 leftOffset += item.Width + Spacing;
             }
             }
 
 
+            // TODO: investigate
             if (renderingCommands.Any(x => x.Measurement.Type == SpacePlanType.Wrap))
             if (renderingCommands.Any(x => x.Measurement.Type == SpacePlanType.Wrap))
                 return renderingCommands;
                 return renderingCommands;
             
             

+ 15 - 0
Source/QuestPDF/Helpers/Helpers.cs

@@ -79,6 +79,21 @@ namespace QuestPDF.Helpers
         {
         {
             element.VisitChildren(x => (x as IDisposable)?.Dispose());
             element.VisitChildren(x => (x as IDisposable)?.Dispose());
         }
         }
+        
+        internal static bool IsGreaterThan(this float first, float second)
+        {
+            return first > second + Size.Epsilon;
+        }
+        
+        internal static bool IsLessThan(this float first, float second)
+        {
+            return first < second - Size.Epsilon;
+        }
+        
+        public static bool AreClose(double a, double b)
+        {
+            return Math.Abs(a - b) <= Size.Epsilon;
+        }
 
 
         internal static bool IsNegative(this Size size)
         internal static bool IsNegative(this Size size)
         {
         {