using System.Text; using UnitTests; using Xunit.Abstractions; namespace DrawingTests.Lines; /// /// Pure unit tests for that don't require Application.Driver or View context. /// These tests focus on properties and behavior that don't depend on glyph rendering. /// Note: Tests that verify rendered output (ToString()) cannot be parallelized because LineCanvas /// depends on Application.Driver for glyph resolution and configuration. Those tests remain in UnitTests. /// public class LineCanvasTests (ITestOutputHelper output) : FakeDriverBase { #region Basic API Tests [Fact] public void Empty_Canvas_ToString_Returns_EmptyString () { LineCanvas canvas = new (); Assert.Equal (string.Empty, canvas.ToString ()); } [Fact] public void Clear_Removes_All_Lines () { LineCanvas canvas = new (); canvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new (0, 0), 3, Orientation.Vertical, LineStyle.Single); canvas.Clear (); Assert.Empty (canvas.Lines); Assert.Equal (Rectangle.Empty, canvas.Bounds); Assert.Equal (string.Empty, canvas.ToString ()); } [Fact] public void Lines_Property_Returns_ReadOnly_Collection () { LineCanvas canvas = new (); canvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single); Assert.Single (canvas.Lines); Assert.IsAssignableFrom> (canvas.Lines); } [Fact] public void AddLine_Adds_Line_To_Collection () { LineCanvas canvas = new (); Assert.Empty (canvas.Lines); canvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single); Assert.Single (canvas.Lines); canvas.AddLine (new (0, 0), 3, Orientation.Vertical, LineStyle.Single); Assert.Equal (2, canvas.Lines.Count); } [Fact] public void Constructor_With_Lines_Creates_Canvas_With_Lines () { StraightLine [] lines = new [] { new StraightLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single), new StraightLine (new (0, 0), 3, Orientation.Vertical, LineStyle.Single) }; var canvas = new LineCanvas (lines); Assert.Equal (2, canvas.Lines.Count); } #endregion #region Bounds Tests - Tests for Bounds property [Theory] [InlineData (0, 0, 0, 0, 0, 1, 1)] [InlineData (0, 0, 1, 0, 0, 1, 1)] [InlineData (0, 0, 2, 0, 0, 2, 2)] [InlineData (0, 0, 3, 0, 0, 3, 3)] [InlineData (0, 0, -1, 0, 0, 1, 1)] [InlineData (0, 0, -2, -1, -1, 2, 2)] [InlineData (0, 0, -3, -2, -2, 3, 3)] public void Viewport_H_And_V_Lines_Both_Positive ( int x, int y, int length, int expectedX, int expectedY, int expectedWidth, int expectedHeight ) { LineCanvas canvas = new (); canvas.AddLine (new (x, y), length, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new (x, y), length, Orientation.Vertical, LineStyle.Single); Assert.Equal (new (expectedX, expectedY, expectedWidth, expectedHeight), canvas.Bounds); } [Theory] [InlineData (0, 0, 0, 0, 0, 1, 1)] [InlineData (0, 0, 1, 0, 0, 1, 1)] [InlineData (0, 0, 2, 0, 0, 2, 1)] [InlineData (0, 0, 3, 0, 0, 3, 1)] [InlineData (0, 0, -1, 0, 0, 1, 1)] [InlineData (0, 0, -2, -1, 0, 2, 1)] [InlineData (0, 0, -3, -2, 0, 3, 1)] public void Viewport_H_Line ( int x, int y, int length, int expectedX, int expectedY, int expectedWidth, int expectedHeight ) { LineCanvas canvas = new (); canvas.AddLine (new (x, y), length, Orientation.Horizontal, LineStyle.Single); Assert.Equal (new (expectedX, expectedY, expectedWidth, expectedHeight), canvas.Bounds); } [Fact] public void Bounds_Specific_Coordinates () { LineCanvas canvas = new (); canvas.AddLine (new (5, 5), 3, Orientation.Horizontal, LineStyle.Single); Assert.Equal (new (5, 5, 3, 1), canvas.Bounds); } [Fact] public void Bounds_Empty_Canvas_Returns_Empty_Rectangle () { LineCanvas canvas = new (); Assert.Equal (Rectangle.Empty, canvas.Bounds); } [Fact] public void Bounds_Single_Point_Zero_Length () { LineCanvas canvas = new (); canvas.AddLine (new (5, 5), 0, Orientation.Horizontal, LineStyle.Single); Assert.Equal (new (5, 5, 1, 1), canvas.Bounds); } [Fact] public void Bounds_Horizontal_Line () { LineCanvas canvas = new (); canvas.AddLine (new (2, 3), 5, Orientation.Horizontal, LineStyle.Single); Assert.Equal (new (2, 3, 5, 1), canvas.Bounds); } [Fact] public void Bounds_Vertical_Line () { LineCanvas canvas = new (); canvas.AddLine (new (2, 3), 5, Orientation.Vertical, LineStyle.Single); Assert.Equal (new (2, 3, 1, 5), canvas.Bounds); } [Fact] public void Bounds_Multiple_Lines_Returns_Union () { LineCanvas canvas = new (); canvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new (0, 0), 3, Orientation.Vertical, LineStyle.Single); Assert.Equal (new (0, 0, 5, 3), canvas.Bounds); } [Fact] public void Bounds_Negative_Length_Line () { LineCanvas canvas = new (); canvas.AddLine (new (5, 5), -3, Orientation.Horizontal, LineStyle.Single); // Line from (5,5) going left 3 positions: includes points 3, 4, 5 (width 3, X starts at 3) Assert.Equal (new (3, 5, 3, 1), canvas.Bounds); } [Fact] public void Bounds_Complex_Box () { LineCanvas canvas = new (); // top canvas.AddLine (new (0, 0), 3, Orientation.Horizontal, LineStyle.Single); // left canvas.AddLine (new (0, 0), 2, Orientation.Vertical, LineStyle.Single); // right canvas.AddLine (new (2, 0), 2, Orientation.Vertical, LineStyle.Single); // bottom canvas.AddLine (new (0, 2), 3, Orientation.Horizontal, LineStyle.Single); Assert.Equal (new (0, 0, 3, 3), canvas.Bounds); } #endregion #region Exclusion Tests [Fact] public void ClearExclusions_Clears_Exclusion_Region () { LineCanvas canvas = new (); canvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single); var region = new Region (new (0, 0, 2, 1)); canvas.Exclude (region); canvas.ClearExclusions (); // After clearing exclusions, GetMap should return all points Dictionary map = canvas.GetMap (); Assert.Equal (5, map.Count); } [Fact] public void Exclude_Removes_Points_From_Map () { LineCanvas canvas = new (); canvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single); var region = new Region (new (0, 0, 2, 1)); canvas.Exclude (region); Dictionary map = canvas.GetMap (); // Should have 5 - 2 = 3 points (excluding the first 2) Assert.Equal (3, map.Count); } #endregion #region Fill Property Tests [Fact] public void Fill_Property_Can_Be_Set () { var foregroundFill = new SolidFill (new (255, 0)); var backgroundFill = new SolidFill (new (0, 0)); var fillPair = new FillPair (foregroundFill, backgroundFill); var canvas = new LineCanvas { Fill = fillPair }; Assert.Equal (fillPair, canvas.Fill); } [Fact] public void Fill_Property_Defaults_To_Null () { LineCanvas canvas = new (); Assert.Null (canvas.Fill); } #endregion [Theory] // Horizontal lines with a vertical zero-length [InlineData ( 0, 0, 1, Orientation.Horizontal, LineStyle.Double, 0, 0, 0, Orientation.Vertical, LineStyle.Single, "╞" )] [InlineData ( 0, 0, -1, Orientation.Horizontal, LineStyle.Double, 0, 0, 0, Orientation.Vertical, LineStyle.Single, "╡" )] [InlineData ( 0, 0, 1, Orientation.Horizontal, LineStyle.Single, 0, 0, 0, Orientation.Vertical, LineStyle.Double, "╟" )] [InlineData ( 0, 0, -1, Orientation.Horizontal, LineStyle.Single, 0, 0, 0, Orientation.Vertical, LineStyle.Double, "╢" )] [InlineData ( 0, 0, 1, Orientation.Horizontal, LineStyle.Single, 0, 0, 0, Orientation.Vertical, LineStyle.Single, "├" )] [InlineData ( 0, 0, -1, Orientation.Horizontal, LineStyle.Single, 0, 0, 0, Orientation.Vertical, LineStyle.Single, "┤" )] [InlineData ( 0, 0, 1, Orientation.Horizontal, LineStyle.Double, 0, 0, 0, Orientation.Vertical, LineStyle.Double, "╠" )] [InlineData ( 0, 0, -1, Orientation.Horizontal, LineStyle.Double, 0, 0, 0, Orientation.Vertical, LineStyle.Double, "╣" )] // Vertical lines with a horizontal zero-length [InlineData ( 0, 0, 1, Orientation.Vertical, LineStyle.Double, 0, 0, 0, Orientation.Horizontal, LineStyle.Single, "╥" )] [InlineData ( 0, 0, -1, Orientation.Vertical, LineStyle.Double, 0, 0, 0, Orientation.Horizontal, LineStyle.Single, "╨" )] [InlineData ( 0, 0, 1, Orientation.Vertical, LineStyle.Single, 0, 0, 0, Orientation.Horizontal, LineStyle.Double, "╤" )] [InlineData ( 0, 0, -1, Orientation.Vertical, LineStyle.Single, 0, 0, 0, Orientation.Horizontal, LineStyle.Double, "╧" )] [InlineData ( 0, 0, 1, Orientation.Vertical, LineStyle.Single, 0, 0, 0, Orientation.Horizontal, LineStyle.Single, "┬" )] [InlineData ( 0, 0, -1, Orientation.Vertical, LineStyle.Single, 0, 0, 0, Orientation.Horizontal, LineStyle.Single, "┴" )] [InlineData ( 0, 0, 1, Orientation.Vertical, LineStyle.Double, 0, 0, 0, Orientation.Horizontal, LineStyle.Double, "╦" )] [InlineData ( 0, 0, -1, Orientation.Vertical, LineStyle.Double, 0, 0, 0, Orientation.Horizontal, LineStyle.Double, "╩" )] // Crosses (two zero-length) [InlineData ( 0, 0, 0, Orientation.Vertical, LineStyle.Double, 0, 0, 0, Orientation.Horizontal, LineStyle.Single, "╫" )] [InlineData ( 0, 0, 0, Orientation.Vertical, LineStyle.Single, 0, 0, 0, Orientation.Horizontal, LineStyle.Double, "╪" )] [InlineData ( 0, 0, 0, Orientation.Vertical, LineStyle.Single, 0, 0, 0, Orientation.Horizontal, LineStyle.Single, "┼" )] [InlineData ( 0, 0, 0, Orientation.Vertical, LineStyle.Double, 0, 0, 0, Orientation.Horizontal, LineStyle.Double, "╬" )] public void Add_2_Lines ( int x1, int y1, int len1, Orientation o1, LineStyle s1, int x2, int y2, int len2, Orientation o2, LineStyle s2, string expected ) { IDriver driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas lc); v.Width = 10; v.Height = 10; v.Viewport = new (0, 0, 10, 10); lc.AddLine (new (x1, y1), len1, o1, s1); lc.AddLine (new (x2, y2), len2, o2, s2); OutputAssert.AssertEqual (output, expected, lc.ToString ()); v.Dispose (); } [Fact] public void Viewport_Specific () { // Draw at 1,1 within client area of View (i.e. leave a top and left margin of 1) // This proves we aren't drawing excess above var x = 1; var y = 2; var width = 3; var height = 2; var lc = new LineCanvas (); // 01230 // ╔╡╞╗1 // ║ ║2 // Add a short horiz line for ╔╡ lc.AddLine (new (x, y), 2, Orientation.Horizontal, LineStyle.Double); Assert.Equal (new (x, y, 2, 1), lc.Bounds); //LHS line down lc.AddLine (new (x, y), height, Orientation.Vertical, LineStyle.Double); Assert.Equal (new (x, y, 2, 2), lc.Bounds); //Vertical line before Title, results in a ╡ lc.AddLine (new (x + 1, y), 0, Orientation.Vertical, LineStyle.Single); Assert.Equal (new (x, y, 2, 2), lc.Bounds); //Vertical line after Title, results in a ╞ lc.AddLine (new (x + 2, y), 0, Orientation.Vertical, LineStyle.Single); Assert.Equal (new (x, y, 3, 2), lc.Bounds); // remainder of top line lc.AddLine (new (x + 2, y), width - 1, Orientation.Horizontal, LineStyle.Double); Assert.Equal (new (x, y, 4, 2), lc.Bounds); //RHS line down lc.AddLine (new (x + width, y), height, Orientation.Vertical, LineStyle.Double); Assert.Equal (new (x, y, 4, 2), lc.Bounds); OutputAssert.AssertEqual ( output, @" ╔╡╞╗ ║ ║", $"{Environment.NewLine}{lc}" ); } [Fact] public void Viewport_Specific_With_Ustring () { // Draw at 1,1 within client area of View (i.e. leave a top and left margin of 1) // This proves we aren't drawing excess above var x = 1; var y = 2; var width = 3; var height = 2; var lc = new LineCanvas (); // 01230 // ╔╡╞╗1 // ║ ║2 // Add a short horiz line for ╔╡ lc.AddLine (new (x, y), 2, Orientation.Horizontal, LineStyle.Double); Assert.Equal (new (x, y, 2, 1), lc.Bounds); //LHS line down lc.AddLine (new (x, y), height, Orientation.Vertical, LineStyle.Double); Assert.Equal (new (x, y, 2, 2), lc.Bounds); //Vertical line before Title, results in a ╡ lc.AddLine (new (x + 1, y), 0, Orientation.Vertical, LineStyle.Single); Assert.Equal (new (x, y, 2, 2), lc.Bounds); //Vertical line after Title, results in a ╞ lc.AddLine (new (x + 2, y), 0, Orientation.Vertical, LineStyle.Single); Assert.Equal (new (x, y, 3, 2), lc.Bounds); // remainder of top line lc.AddLine (new (x + 2, y), width - 1, Orientation.Horizontal, LineStyle.Double); Assert.Equal (new (x, y, 4, 2), lc.Bounds); //RHS line down lc.AddLine (new (x + width, y), height, Orientation.Vertical, LineStyle.Double); Assert.Equal (new (x, y, 4, 2), lc.Bounds); OutputAssert.AssertEqual ( output, @" ╔╡╞╗ ║ ║", $"{Environment.NewLine}{lc}" ); } [Fact] public void Canvas_Updates_On_Changes () { var lc = new LineCanvas (); Assert.Equal (Rectangle.Empty, lc.Bounds); lc.AddLine (Point.Empty, 2, Orientation.Horizontal, LineStyle.Double); Assert.NotEqual (Rectangle.Empty, lc.Bounds); lc.Clear (); Assert.Equal (Rectangle.Empty, lc.Bounds); } [InlineData (0, 0, Orientation.Horizontal, "─")] [InlineData (1, 0, Orientation.Horizontal, "─")] [InlineData (0, 1, Orientation.Horizontal, "─")] [InlineData (-1, 0, Orientation.Horizontal, "─")] [InlineData (0, -1, Orientation.Horizontal, "─")] [InlineData (-1, -1, Orientation.Horizontal, "─")] [InlineData (0, 0, Orientation.Vertical, "│")] [InlineData (1, 0, Orientation.Vertical, "│")] [InlineData (0, 1, Orientation.Vertical, "│")] [InlineData (0, -1, Orientation.Vertical, "│")] [InlineData (-1, 0, Orientation.Vertical, "│")] [InlineData (-1, -1, Orientation.Vertical, "│")] [Theory] public void Length_0_Is_1_Long (int x, int y, Orientation orientation, string expected) { LineCanvas canvas = new (); // Add a line at 5, 5 that's has length of 1 canvas.AddLine (new (x, y), 1, orientation, LineStyle.Single); OutputAssert.AssertEqual (output, $"{expected}", $"{canvas}"); } // X is offset by 2 [InlineData (0, 0, 1, Orientation.Horizontal, "─")] [InlineData (1, 0, 1, Orientation.Horizontal, "─")] [InlineData (0, 1, 1, Orientation.Horizontal, "─")] [InlineData (0, 0, 1, Orientation.Vertical, "│")] [InlineData (1, 0, 1, Orientation.Vertical, "│")] [InlineData (0, 1, 1, Orientation.Vertical, "│")] [InlineData (-1, 0, 1, Orientation.Horizontal, "─")] [InlineData (0, -1, 1, Orientation.Horizontal, "─")] [InlineData (-1, 0, 1, Orientation.Vertical, "│")] [InlineData (0, -1, 1, Orientation.Vertical, "│")] [InlineData (0, 0, -1, Orientation.Horizontal, "─")] [InlineData (1, 0, -1, Orientation.Horizontal, "─")] [InlineData (0, 1, -1, Orientation.Horizontal, "─")] [InlineData (0, 0, -1, Orientation.Vertical, "│")] [InlineData (1, 0, -1, Orientation.Vertical, "│")] [InlineData (0, 1, -1, Orientation.Vertical, "│")] [InlineData (-1, 0, -1, Orientation.Horizontal, "─")] [InlineData (0, -1, -1, Orientation.Horizontal, "─")] [InlineData (-1, 0, -1, Orientation.Vertical, "│")] [InlineData (0, -1, -1, Orientation.Vertical, "│")] [InlineData (0, 0, 2, Orientation.Horizontal, "──")] [InlineData (1, 0, 2, Orientation.Horizontal, "──")] [InlineData (0, 1, 2, Orientation.Horizontal, "──")] [InlineData (1, 1, 2, Orientation.Horizontal, "──")] [InlineData (0, 0, 2, Orientation.Vertical, "│\r\n│")] [InlineData (1, 0, 2, Orientation.Vertical, "│\r\n│")] [InlineData (0, 1, 2, Orientation.Vertical, "│\r\n│")] [InlineData (1, 1, 2, Orientation.Vertical, "│\r\n│")] [InlineData (-1, 0, 2, Orientation.Horizontal, "──")] [InlineData (0, -1, 2, Orientation.Horizontal, "──")] [InlineData (-1, 0, 2, Orientation.Vertical, "│\r\n│")] [InlineData (0, -1, 2, Orientation.Vertical, "│\r\n│")] [InlineData (-1, -1, 2, Orientation.Vertical, "│\r\n│")] [InlineData (0, 0, -2, Orientation.Horizontal, "──")] [InlineData (1, 0, -2, Orientation.Horizontal, "──")] [InlineData (0, 1, -2, Orientation.Horizontal, "──")] [InlineData (0, 0, -2, Orientation.Vertical, "│\r\n│")] [InlineData (1, 0, -2, Orientation.Vertical, "│\r\n│")] [InlineData (0, 1, -2, Orientation.Vertical, "│\r\n│")] [InlineData (1, 1, -2, Orientation.Vertical, "│\r\n│")] [InlineData (-1, 0, -2, Orientation.Horizontal, "──")] [InlineData (0, -1, -2, Orientation.Horizontal, "──")] [InlineData (-1, 0, -2, Orientation.Vertical, "│\r\n│")] [InlineData (0, -1, -2, Orientation.Vertical, "│\r\n│")] [InlineData (-1, -1, -2, Orientation.Vertical, "│\r\n│")] [Theory] public void Length_n_Is_n_Long (int x, int y, int length, Orientation orientation, string expected) { LineCanvas canvas = new (); canvas.AddLine (new (x, y), length, orientation, LineStyle.Single); var result = canvas.ToString (); OutputAssert.AssertEqual (output, expected, result); } [Fact] public void Length_Negative () { var offset = new Point (5, 5); LineCanvas canvas = new (); canvas.AddLine (offset, -3, Orientation.Horizontal, LineStyle.Single); var looksLike = "───"; Assert.Equal (looksLike, $"{canvas}"); } [InlineData (Orientation.Horizontal, "─")] [InlineData (Orientation.Vertical, "│")] [Theory] public void Length_Zero_Alone_Is_Line (Orientation orientation, string expected) { var lc = new LineCanvas (); // Add a line at 0, 0 that's has length of 0 lc.AddLine (Point.Empty, 0, orientation, LineStyle.Single); OutputAssert.AssertEqual (output, expected, $"{lc}"); } [InlineData (Orientation.Horizontal, "┼")] [InlineData (Orientation.Vertical, "┼")] [Theory] public void Length_Zero_Cross_Is_Cross (Orientation orientation, string expected) { var lc = new LineCanvas (); // Add point at opposite orientation lc.AddLine ( Point.Empty, 0, orientation == Orientation.Horizontal ? Orientation.Vertical : Orientation.Horizontal, LineStyle.Single ); // Add a line at 0, 0 that's has length of 0 lc.AddLine (Point.Empty, 0, orientation, LineStyle.Single); OutputAssert.AssertEqual (output, expected, $"{lc}"); } [InlineData (Orientation.Horizontal, "╥")] [InlineData (Orientation.Vertical, "╞")] [Theory] public void Length_Zero_NextTo_Opposite_Is_T (Orientation orientation, string expected) { var lc = new LineCanvas (); // Add line with length of 1 in opposite orientation starting at same location if (orientation == Orientation.Horizontal) { lc.AddLine (Point.Empty, 1, Orientation.Vertical, LineStyle.Double); } else { lc.AddLine (Point.Empty, 1, Orientation.Horizontal, LineStyle.Double); } // Add a line at 0, 0 that's has length of 0 lc.AddLine (Point.Empty, 0, orientation, LineStyle.Single); OutputAssert.AssertEqual (output, expected, $"{lc}"); } [Fact] public void TestLineCanvas_LeaveMargin_Top1_Left1 () { LineCanvas canvas = new (); // Upper box canvas.AddLine (Point.Empty, 2, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (Point.Empty, 2, Orientation.Vertical, LineStyle.Single); var looksLike = @" ┌─ │ "; OutputAssert.AssertEqual (output, looksLike, $"{Environment.NewLine}{canvas}"); } [Fact] public void TestLineCanvas_Window_Heavy () { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); // outer box canvas.AddLine (Point.Empty, 10, Orientation.Horizontal, LineStyle.Heavy); canvas.AddLine (new (9, 0), 5, Orientation.Vertical, LineStyle.Heavy); canvas.AddLine (new (9, 4), -10, Orientation.Horizontal, LineStyle.Heavy); canvas.AddLine (new (0, 4), -5, Orientation.Vertical, LineStyle.Heavy); canvas.AddLine (new (5, 0), 5, Orientation.Vertical, LineStyle.Heavy); canvas.AddLine (new (0, 2), 10, Orientation.Horizontal, LineStyle.Heavy); v.Draw (); var looksLike = @" ┏━━━━┳━━━┓ ┃ ┃ ┃ ┣━━━━╋━━━┫ ┃ ┃ ┃ ┗━━━━┻━━━┛"; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } [Theory] [InlineData (LineStyle.Single)] [InlineData (LineStyle.Rounded)] public void TestLineCanvas_Window_HeavyTop_ThinSides (LineStyle thinStyle) { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); // outer box canvas.AddLine (Point.Empty, 10, Orientation.Horizontal, LineStyle.Heavy); canvas.AddLine (new (9, 0), 5, Orientation.Vertical, thinStyle); canvas.AddLine (new (9, 4), -10, Orientation.Horizontal, LineStyle.Heavy); canvas.AddLine (new (0, 4), -5, Orientation.Vertical, thinStyle); canvas.AddLine (new (5, 0), 5, Orientation.Vertical, thinStyle); canvas.AddLine (new (0, 2), 10, Orientation.Horizontal, LineStyle.Heavy); v.Draw (); var looksLike = @" ┍━━━━┯━━━┑ │ │ │ ┝━━━━┿━━━┥ │ │ │ ┕━━━━┷━━━┙ "; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } [Theory] [InlineData (LineStyle.Single)] [InlineData (LineStyle.Rounded)] public void TestLineCanvas_Window_ThinTop_HeavySides (LineStyle thinStyle) { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); // outer box canvas.AddLine (Point.Empty, 10, Orientation.Horizontal, thinStyle); canvas.AddLine (new (9, 0), 5, Orientation.Vertical, LineStyle.Heavy); canvas.AddLine (new (9, 4), -10, Orientation.Horizontal, thinStyle); canvas.AddLine (new (0, 4), -5, Orientation.Vertical, LineStyle.Heavy); canvas.AddLine (new (5, 0), 5, Orientation.Vertical, LineStyle.Heavy); canvas.AddLine (new (0, 2), 10, Orientation.Horizontal, thinStyle); v.Draw (); var looksLike = @" ┎────┰───┒ ┃ ┃ ┃ ┠────╂───┨ ┃ ┃ ┃ ┖────┸───┚ "; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } [Fact] public void Top_Left_From_TopRight_LeftUp () { LineCanvas canvas = new (); // Upper box canvas.AddLine (Point.Empty, 2, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new (0, 1), -2, Orientation.Vertical, LineStyle.Single); var looksLike = @" ┌─ │ "; OutputAssert.AssertEqual (output, looksLike, $"{Environment.NewLine}{canvas}"); } [Fact] public void Top_With_1Down () { LineCanvas canvas = new (); // Top ─ canvas.AddLine (Point.Empty, 1, Orientation.Horizontal, LineStyle.Single); // Bottom ─ canvas.AddLine (new (1, 1), -1, Orientation.Horizontal, LineStyle.Single); //// Right down //canvas.AddLine (new Point (9, 0), 3, Orientation.Vertical, LineStyle.Single); //// Bottom //canvas.AddLine (new Point (9, 3), -10, Orientation.Horizontal, LineStyle.Single); //// Left Up //canvas.AddLine (new Point (0, 3), -3, Orientation.Vertical, LineStyle.Single); Assert.Equal (new (0, 0, 2, 2), canvas.Bounds); Dictionary map = canvas.GetMap (); Assert.Equal (2, map.Count); OutputAssert.AssertEqual ( output, @" ─ ─", $"{Environment.NewLine}{canvas}" ); } [Fact] public void ToString_Empty () { var lc = new LineCanvas (); OutputAssert.AssertEqual (output, string.Empty, lc.ToString ()); } // 012 [InlineData (0, 0, "═══")] [InlineData (1, 0, "═══")] [InlineData (0, 1, "═══")] [InlineData (1, 1, "═══")] [InlineData (2, 2, "═══")] [InlineData (-1, 0, "═══")] [InlineData (0, -1, "═══")] [InlineData (-1, -1, "═══")] [InlineData (-2, -2, "═══")] [Theory] public void ToString_Positive_Horizontal_1Line_Offset (int x, int y, string expected) { var lc = new LineCanvas (); lc.AddLine (new (x, y), 3, Orientation.Horizontal, LineStyle.Double); OutputAssert.AssertEqual (output, expected, $"{lc}"); } [InlineData (0, 0, 0, 0, "═══")] [InlineData (1, 0, 1, 0, "═══")] [InlineData (-1, 0, -1, 0, "═══")] [InlineData (0, 0, 1, 0, "════")] [InlineData (1, 0, 3, 0, "═════")] [InlineData (1, 0, 4, 0, "══════")] [InlineData (1, 0, 5, 0, "═══ ═══")] [InlineData (0, 0, 0, 1, "\u2550\u2550\u2550\r\n\u2550\u2550\u2550")] [InlineData (0, 0, 1, 1, "═══ \r\n ═══")] [InlineData (0, 0, 2, 1, "═══ \r\n ═══")] [InlineData (1, 0, 0, 1, " ═══\r\n═══ ")] [InlineData (0, 1, 0, 1, "═══")] [InlineData (1, 1, 0, 1, "════")] [InlineData (2, 2, 0, 1, "═══ \r\n ═══")] [Theory] public void ToString_Positive_Horizontal_2Line_Offset (int x1, int y1, int x2, int y2, string expected) { var lc = new LineCanvas (); lc.AddLine (new (x1, y1), 3, Orientation.Horizontal, LineStyle.Double); lc.AddLine (new (x2, y2), 3, Orientation.Horizontal, LineStyle.Double); OutputAssert.AssertEqual (output, expected, $"{lc}"); } // [Fact, SetupFakeDriver] // public void LeaveMargin_Top1_Left1 () // { // var canvas = new LineCanvas (); // // Upper box // canvas.AddLine (Point.Empty, 9, Orientation.Horizontal, LineStyle.Single); // canvas.AddLine (new Point (8, 0), 3, Orientation.Vertical, LineStyle.Single); // canvas.AddLine (new Point (8, 3), -9, Orientation.Horizontal, LineStyle.Single); // canvas.AddLine (new Point (0, 2), -3, Orientation.Vertical, LineStyle.Single); // // Lower Box // canvas.AddLine (new Point (5, 0), 2, Orientation.Vertical, LineStyle.Single); // canvas.AddLine (new Point (0, 2), 9, Orientation.Horizontal, LineStyle.Single); // string looksLike = //@" //┌────┬──┐ //│ │ │ //├────┼──┤ //└────┴──┘ //"; // Assert.Equal (looksLike, $"{Environment.NewLine}{canvas}"); // } [InlineData (0, 0, 0, Orientation.Horizontal, LineStyle.Double, "═")] [InlineData (0, 0, 0, Orientation.Vertical, LineStyle.Double, "║")] [InlineData (0, 0, 0, Orientation.Horizontal, LineStyle.Single, "─")] [InlineData (0, 0, 0, Orientation.Vertical, LineStyle.Single, "│")] [InlineData (0, 0, 1, Orientation.Horizontal, LineStyle.Double, "═")] [InlineData (0, 0, 1, Orientation.Vertical, LineStyle.Double, "║")] [InlineData (0, 0, 1, Orientation.Horizontal, LineStyle.Single, "─")] [InlineData (0, 0, 1, Orientation.Vertical, LineStyle.Single, "│")] [InlineData (0, 0, 2, Orientation.Horizontal, LineStyle.Double, "══")] [InlineData (0, 0, 2, Orientation.Vertical, LineStyle.Double, "║\n║")] [InlineData (0, 0, 2, Orientation.Horizontal, LineStyle.Single, "──")] [InlineData (0, 0, 2, Orientation.Vertical, LineStyle.Single, "│\n│")] [Theory] public void View_Draws_1LineTests ( int x1, int y1, int length, Orientation o1, LineStyle s1, string expected ) { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas lc); v.Width = 10; v.Height = 10; v.Viewport = new (0, 0, 10, 10); lc.AddLine (new (x1, y1), length, o1, s1); v.Draw (); DriverAssert.AssertDriverContentsAre (expected, output, driver); v.Dispose (); } /// This test demonstrates how to correctly trigger a corner. By overlapping the lines in the same cell [Fact] public void View_Draws_Corner_Correct () { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); canvas.AddLine (Point.Empty, 2, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (Point.Empty, 2, Orientation.Vertical, LineStyle.Single); v.Draw (); var looksLike = @" ┌─ │"; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } /// /// This test demonstrates that corners are only drawn when lines overlap. Not when they terminate adjacent to one /// another. /// [Fact] public void View_Draws_Corner_NoOverlap () { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); canvas.AddLine (Point.Empty, 2, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new (0, 1), 2, Orientation.Vertical, LineStyle.Single); v.Draw (); var looksLike = @" ── │ │"; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } [InlineData (LineStyle.Single)] [InlineData (LineStyle.Rounded)] [Theory] public void View_Draws_Horizontal (LineStyle style) { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); canvas.AddLine (Point.Empty, 2, Orientation.Horizontal, style); v.Draw (); var looksLike = @" ──"; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } [Fact] public void View_Draws_Horizontal_Double () { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); canvas.AddLine (Point.Empty, 2, Orientation.Horizontal, LineStyle.Double); v.Draw (); var looksLike = @" ══"; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } [InlineData (LineStyle.Single)] [InlineData (LineStyle.Rounded)] [Theory] public void View_Draws_Vertical (LineStyle style) { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); canvas.AddLine (Point.Empty, 2, Orientation.Vertical, style); v.Draw (); var looksLike = @" │ │"; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } [Fact] public void View_Draws_Vertical_Double () { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); canvas.AddLine (Point.Empty, 2, Orientation.Vertical, LineStyle.Double); v.Draw (); var looksLike = @" ║ ║"; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } [Fact] public void View_Draws_Window_Double () { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); // outer box canvas.AddLine (Point.Empty, 10, Orientation.Horizontal, LineStyle.Double); canvas.AddLine (new (9, 0), 5, Orientation.Vertical, LineStyle.Double); canvas.AddLine (new (9, 4), -10, Orientation.Horizontal, LineStyle.Double); canvas.AddLine (new (0, 4), -5, Orientation.Vertical, LineStyle.Double); canvas.AddLine (new (5, 0), 5, Orientation.Vertical, LineStyle.Double); canvas.AddLine (new (0, 2), 10, Orientation.Horizontal, LineStyle.Double); v.Draw (); var looksLike = @" ╔════╦═══╗ ║ ║ ║ ╠════╬═══╣ ║ ║ ║ ╚════╩═══╝"; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } [Theory] [InlineData (LineStyle.Single)] [InlineData (LineStyle.Rounded)] public void View_Draws_Window_DoubleTop_SingleSides (LineStyle thinStyle) { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); // outer box canvas.AddLine (Point.Empty, 10, Orientation.Horizontal, LineStyle.Double); canvas.AddLine (new (9, 0), 5, Orientation.Vertical, thinStyle); canvas.AddLine (new (9, 4), -10, Orientation.Horizontal, LineStyle.Double); canvas.AddLine (new (0, 4), -5, Orientation.Vertical, thinStyle); canvas.AddLine (new (5, 0), 5, Orientation.Vertical, thinStyle); canvas.AddLine (new (0, 2), 10, Orientation.Horizontal, LineStyle.Double); v.Draw (); var looksLike = @" ╒════╤═══╕ │ │ │ ╞════╪═══╡ │ │ │ ╘════╧═══╛ "; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } /// /// Demonstrates when corners are used. Notice how not all lines declare rounded. /// If there are 1+ lines intersecting and a corner is to be used then if any of them are rounded a rounded corner is /// used. /// [Fact] public void View_Draws_Window_Rounded () { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); // outer box canvas.AddLine (Point.Empty, 10, Orientation.Horizontal, LineStyle.Rounded); // LineStyle.Single is ignored because corner overlaps with the above line which is Rounded // this results in a rounded corner being used. canvas.AddLine (new (9, 0), 5, Orientation.Vertical, LineStyle.Single); canvas.AddLine (new (9, 4), -10, Orientation.Horizontal, LineStyle.Rounded); canvas.AddLine (new (0, 4), -5, Orientation.Vertical, LineStyle.Single); // These lines say rounded but they will result in the T sections which are never rounded. canvas.AddLine (new (5, 0), 5, Orientation.Vertical, LineStyle.Rounded); canvas.AddLine (new (0, 2), 10, Orientation.Horizontal, LineStyle.Rounded); v.Draw (); var looksLike = @" ╭────┬───╮ │ │ │ ├────┼───┤ │ │ │ ╰────┴───╯"; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } [Theory] [InlineData (LineStyle.Single)] [InlineData (LineStyle.Rounded)] public void View_Draws_Window_SingleTop_DoubleSides (LineStyle thinStyle) { var driver = CreateFakeDriver (); View v = GetCanvas (driver, out LineCanvas canvas); // outer box canvas.AddLine (Point.Empty, 10, Orientation.Horizontal, thinStyle); canvas.AddLine (new (9, 0), 5, Orientation.Vertical, LineStyle.Double); canvas.AddLine (new (9, 4), -10, Orientation.Horizontal, thinStyle); canvas.AddLine (new (0, 4), -5, Orientation.Vertical, LineStyle.Double); canvas.AddLine (new (5, 0), 5, Orientation.Vertical, LineStyle.Double); canvas.AddLine (new (0, 2), 10, Orientation.Horizontal, thinStyle); v.Draw (); var looksLike = @" ╓────╥───╖ ║ ║ ║ ╟────╫───╢ ║ ║ ║ ╙────╨───╜ "; DriverAssert.AssertDriverContentsAre (looksLike, output, driver); v.Dispose (); } [Fact] public void Window () { LineCanvas canvas = new (); // Frame canvas.AddLine (Point.Empty, 10, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new (9, 0), 5, Orientation.Vertical, LineStyle.Single); canvas.AddLine (new (9, 4), -10, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new (0, 4), -5, Orientation.Vertical, LineStyle.Single); // Cross canvas.AddLine (new (5, 0), 5, Orientation.Vertical, LineStyle.Single); canvas.AddLine (new (0, 2), 10, Orientation.Horizontal, LineStyle.Single); var looksLike = @" ┌────┬───┐ │ │ │ ├────┼───┤ │ │ │ └────┴───┘"; OutputAssert.AssertEqual (output, looksLike, $"{Environment.NewLine}{canvas}"); } [Fact] public void Zero_Length_Intersections () { // Draw at 1,2 within client area of View (i.e. leave a top and left margin of 1) // This proves we aren't drawing excess above var x = 1; var y = 2; var width = 5; var height = 2; var lc = new LineCanvas (); // ╔╡╞═════╗ // Add a short horiz line for ╔╡ lc.AddLine (new (x, y), 2, Orientation.Horizontal, LineStyle.Double); //LHS line down lc.AddLine (new (x, y), height, Orientation.Vertical, LineStyle.Double); //Vertical line before Title, results in a ╡ lc.AddLine (new (x + 1, y), 0, Orientation.Vertical, LineStyle.Single); //Vertical line after Title, results in a ╞ lc.AddLine (new (x + 2, y), 0, Orientation.Vertical, LineStyle.Single); // remainder of top line lc.AddLine (new (x + 2, y), width - 1, Orientation.Horizontal, LineStyle.Double); //RHS line down lc.AddLine (new (x + width, y), height, Orientation.Vertical, LineStyle.Double); var looksLike = @" ╔╡╞══╗ ║ ║"; OutputAssert.AssertEqual (output, looksLike, $"{Environment.NewLine}{lc}"); } [Fact] public void LineCanvas_UsesFillCorrectly () { // Arrange var foregroundColor = new Color (255, 0); // Red var backgroundColor = new Color (0, 0); // Black var foregroundFill = new SolidFill (foregroundColor); var backgroundFill = new SolidFill (backgroundColor); var fillPair = new FillPair (foregroundFill, backgroundFill); var lineCanvas = new LineCanvas { Fill = fillPair }; // Act lineCanvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single); Dictionary cellMap = lineCanvas.GetCellMap (); // Assert foreach (Cell? cell in cellMap.Values) { Assert.NotNull (cell); Assert.Equal (foregroundColor, cell.Value.Attribute!.Value.Foreground); Assert.Equal (backgroundColor, cell.Value.Attribute.Value.Background); } } [Fact] public void LineCanvas_LineColorIgnoredBecauseOfFill () { // Arrange var foregroundColor = new Color (255, 0); // Red var backgroundColor = new Color (0, 0); // Black var lineColor = new Attribute (new Color (0, 255), new Color (255, 255, 255)); // Green on White var foregroundFill = new SolidFill (foregroundColor); var backgroundFill = new SolidFill (backgroundColor); var fillPair = new FillPair (foregroundFill, backgroundFill); var lineCanvas = new LineCanvas { Fill = fillPair }; // Act lineCanvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single, lineColor); Dictionary cellMap = lineCanvas.GetCellMap (); // Assert foreach (Cell? cell in cellMap.Values) { Assert.NotNull (cell); Assert.Equal (foregroundColor, cell.Value.Attribute!.Value.Foreground); Assert.Equal (backgroundColor, cell.Value.Attribute.Value.Background); } } [Fact] public void LineCanvas_IntersectingLinesUseFillCorrectly () { // Arrange var foregroundColor = new Color (255, 0); // Red var backgroundColor = new Color (0, 0); // Black var foregroundFill = new SolidFill (foregroundColor); var backgroundFill = new SolidFill (backgroundColor); var fillPair = new FillPair (foregroundFill, backgroundFill); var lineCanvas = new LineCanvas { Fill = fillPair }; // Act lineCanvas.AddLine (new (0, 0), 5, Orientation.Horizontal, LineStyle.Single); lineCanvas.AddLine (new (2, -2), 5, Orientation.Vertical, LineStyle.Single); Dictionary cellMap = lineCanvas.GetCellMap (); // Assert foreach (Cell? cell in cellMap.Values) { Assert.NotNull (cell); Assert.Equal (foregroundColor, cell.Value.Attribute!.Value.Foreground); Assert.Equal (backgroundColor, cell.Value.Attribute.Value.Background); } } // TODO: Remove this and make all LineCanvas tests independent of View /// /// Creates a new into which a is rendered at /// time. /// /// The you can draw into. /// How far to offset drawing in X /// How far to offset drawing in Y /// private View GetCanvas (IDriver driver, out LineCanvas canvas, int offsetX = 0, int offsetY = 0) { var v = new View { Width = 10, Height = 5, Viewport = new (0, 0, 10, 5) }; v.Driver = driver; LineCanvas canvasCopy = canvas = new (); v.DrawComplete += (s, e) => { v.FillRect (v.Viewport); foreach (KeyValuePair p in canvasCopy.GetMap ()) { v.AddRune ( offsetX + p.Key.X, offsetY + p.Key.Y, p.Value ); } canvasCopy.Clear (); }; return v; } #region GetRegion Tests [Fact] public void GetRegion_EmptyCellMap_ReturnsEmptyRegion () { Dictionary cellMap = new (); Region region = LineCanvas.GetRegion (cellMap); Assert.NotNull (region); Assert.True (region.IsEmpty ()); } [Fact] public void GetRegion_SingleCell_ReturnsSingleRectangle () { Dictionary cellMap = new () { { new Point (5, 10), new Cell { Grapheme = "X" } } }; Region region = LineCanvas.GetRegion (cellMap); Assert.NotNull (region); Assert.False (region.IsEmpty ()); Assert.True (region.Contains (5, 10)); } [Fact] public void GetRegion_HorizontalLine_CreatesHorizontalSpan () { Dictionary cellMap = new (); // Horizontal line from (5, 10) to (9, 10) for (int x = 5; x <= 9; x++) { cellMap.Add (new Point (x, 10), new Cell { Grapheme = "─" }); } Region region = LineCanvas.GetRegion (cellMap); Assert.NotNull (region); // All cells in the horizontal span should be in the region for (int x = 5; x <= 9; x++) { Assert.True (region.Contains (x, 10), $"Expected ({x}, 10) to be in region"); } // Cells outside the span should not be in the region Assert.False (region.Contains (4, 10)); Assert.False (region.Contains (10, 10)); Assert.False (region.Contains (7, 9)); Assert.False (region.Contains (7, 11)); } [Fact] public void GetRegion_VerticalLine_CreatesMultipleHorizontalSpans () { Dictionary cellMap = new (); // Vertical line from (5, 10) to (5, 14) for (int y = 10; y <= 14; y++) { cellMap.Add (new Point (5, y), new Cell { Grapheme = "│" }); } Region region = LineCanvas.GetRegion (cellMap); Assert.NotNull (region); // All cells in the vertical line should be in the region for (int y = 10; y <= 14; y++) { Assert.True (region.Contains (5, y), $"Expected (5, {y}) to be in region"); } // Cells outside should not be in the region Assert.False (region.Contains (4, 12)); Assert.False (region.Contains (6, 12)); } [Fact] public void GetRegion_LShape_CreatesCorrectSpans () { Dictionary cellMap = new (); // L-shape: horizontal line from (0, 0) to (5, 0), then vertical to (5, 3) for (int x = 0; x <= 5; x++) { cellMap.Add (new Point (x, 0), new Cell { Grapheme = "─" }); } for (int y = 1; y <= 3; y++) { cellMap.Add (new Point (5, y), new Cell { Grapheme = "│" }); } Region region = LineCanvas.GetRegion (cellMap); // Horizontal part for (int x = 0; x <= 5; x++) { Assert.True (region.Contains (x, 0), $"Expected ({x}, 0) to be in region"); } // Vertical part for (int y = 1; y <= 3; y++) { Assert.True (region.Contains (5, y), $"Expected (5, {y}) to be in region"); } // Empty cells should not be in region Assert.False (region.Contains (1, 1)); Assert.False (region.Contains (4, 2)); } [Fact] public void GetRegion_DiscontiguousHorizontalCells_CreatesSeparateSpans () { Dictionary cellMap = new () { { new Point (0, 5), new Cell { Grapheme = "X" } }, { new Point (1, 5), new Cell { Grapheme = "X" } }, // Gap at (2, 5) { new Point (3, 5), new Cell { Grapheme = "X" } }, { new Point (4, 5), new Cell { Grapheme = "X" } } }; Region region = LineCanvas.GetRegion (cellMap); Assert.True (region.Contains (0, 5)); Assert.True (region.Contains (1, 5)); Assert.False (region.Contains (2, 5)); // Gap Assert.True (region.Contains (3, 5)); Assert.True (region.Contains (4, 5)); } [Fact] public void GetRegion_IntersectingLines_CreatesCorrectRegion () { Dictionary cellMap = new (); // Horizontal line for (int x = 0; x <= 4; x++) { cellMap.Add (new Point (x, 2), new Cell { Grapheme = "─" }); } // Vertical line intersecting at (2, 2) for (int y = 0; y <= 4; y++) { cellMap [new Point (2, y)] = new Cell { Grapheme = "┼" }; } Region region = LineCanvas.GetRegion (cellMap); // Horizontal line for (int x = 0; x <= 4; x++) { Assert.True (region.Contains (x, 2), $"Expected ({x}, 2) to be in region"); } // Vertical line for (int y = 0; y <= 4; y++) { Assert.True (region.Contains (2, y), $"Expected (2, {y}) to be in region"); } } #endregion #region GetCellMapWithRegion Tests [Fact] public void GetCellMapWithRegion_EmptyCanvas_ReturnsEmptyMapAndRegion () { LineCanvas canvas = new (); (Dictionary cellMap, Region region) = canvas.GetCellMapWithRegion (); Assert.NotNull (cellMap); Assert.Empty (cellMap); Assert.NotNull (region); Assert.True (region.IsEmpty ()); } [Fact] public void GetCellMapWithRegion_SingleHorizontalLine_ReturnsCellMapAndRegion () { LineCanvas canvas = new (); canvas.AddLine (new Point (5, 10), 5, Orientation.Horizontal, LineStyle.Single); (Dictionary cellMap, Region region) = canvas.GetCellMapWithRegion (); Assert.NotNull (cellMap); Assert.NotEmpty (cellMap); Assert.NotNull (region); Assert.False (region.IsEmpty ()); // Both cellMap and region should contain the same cells foreach (Point p in cellMap.Keys) { Assert.True (region.Contains (p.X, p.Y), $"Expected ({p.X}, {p.Y}) to be in region"); } } [Fact] public void GetCellMapWithRegion_SingleVerticalLine_ReturnsCellMapAndRegion () { LineCanvas canvas = new (); canvas.AddLine (new Point (5, 10), 5, Orientation.Vertical, LineStyle.Single); (Dictionary cellMap, Region region) = canvas.GetCellMapWithRegion (); Assert.NotNull (cellMap); Assert.NotEmpty (cellMap); Assert.NotNull (region); Assert.False (region.IsEmpty ()); // Both cellMap and region should contain the same cells foreach (Point p in cellMap.Keys) { Assert.True (region.Contains (p.X, p.Y), $"Expected ({p.X}, {p.Y}) to be in region"); } } [Fact] public void GetCellMapWithRegion_IntersectingLines_CorrectlyHandlesIntersection () { LineCanvas canvas = new (); // Create a cross pattern canvas.AddLine (new Point (0, 2), 5, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new Point (2, 0), 5, Orientation.Vertical, LineStyle.Single); (Dictionary cellMap, Region region) = canvas.GetCellMapWithRegion (); Assert.NotNull (cellMap); Assert.NotEmpty (cellMap); Assert.NotNull (region); // Verify intersection point is in both Assert.True (cellMap.ContainsKey (new Point (2, 2)), "Intersection should be in cellMap"); Assert.True (region.Contains (2, 2), "Intersection should be in region"); // All cells should be in both structures foreach (Point p in cellMap.Keys) { Assert.True (region.Contains (p.X, p.Y), $"Expected ({p.X}, {p.Y}) to be in region"); } } [Fact] public void GetCellMapWithRegion_ComplexShape_RegionMatchesCellMap () { LineCanvas canvas = new (); // Create a box canvas.AddLine (new Point (0, 0), 5, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new Point (0, 3), 5, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new Point (0, 0), 4, Orientation.Vertical, LineStyle.Single); canvas.AddLine (new Point (4, 0), 4, Orientation.Vertical, LineStyle.Single); (Dictionary cellMap, Region region) = canvas.GetCellMapWithRegion (); Assert.NotNull (cellMap); Assert.NotEmpty (cellMap); Assert.NotNull (region); // Every cell in the map should be in the region foreach (Point p in cellMap.Keys) { Assert.True (region.Contains (p.X, p.Y), $"Expected ({p.X}, {p.Y}) to be in region"); } // Cells not in the map should not be in the region (interior of box) Assert.False (cellMap.ContainsKey (new Point (2, 1))); // Note: Region might contain interior if it's filled, so we just verify consistency } [Fact] public void GetCellMapWithRegion_ResultsMatchSeparateCalls () { LineCanvas canvas = new (); // Create a complex pattern canvas.AddLine (new Point (0, 0), 10, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new Point (5, 0), 10, Orientation.Vertical, LineStyle.Single); canvas.AddLine (new Point (0, 5), 10, Orientation.Horizontal, LineStyle.Double); // Get results from combined method (Dictionary combinedCellMap, Region combinedRegion) = canvas.GetCellMapWithRegion (); // Get results from separate calls Dictionary separateCellMap = canvas.GetCellMap (); Region separateRegion = LineCanvas.GetRegion (separateCellMap); // Cell maps should be identical Assert.Equal (separateCellMap.Count, combinedCellMap.Count); foreach (KeyValuePair kvp in separateCellMap) { Assert.True (combinedCellMap.ContainsKey (kvp.Key), $"Combined map missing key {kvp.Key}"); } // Regions should contain the same points foreach (Point p in combinedCellMap.Keys) { Assert.True (combinedRegion.Contains (p.X, p.Y), $"Combined region missing ({p.X}, {p.Y})"); Assert.True (separateRegion.Contains (p.X, p.Y), $"Separate region missing ({p.X}, {p.Y})"); } } [Fact] public void GetCellMapWithRegion_NegativeCoordinates_HandlesCorrectly () { LineCanvas canvas = new (); canvas.AddLine (new Point (-5, -5), 10, Orientation.Horizontal, LineStyle.Single); canvas.AddLine (new Point (0, -5), 10, Orientation.Vertical, LineStyle.Single); (Dictionary cellMap, Region region) = canvas.GetCellMapWithRegion (); Assert.NotNull (cellMap); Assert.NotEmpty (cellMap); Assert.NotNull (region); // Verify negative coordinates are handled Assert.True (cellMap.Keys.Any (p => p.X < 0 || p.Y < 0), "Should have negative coordinates"); // All cells should be in region foreach (Point p in cellMap.Keys) { Assert.True (region.Contains (p.X, p.Y), $"Expected ({p.X}, {p.Y}) to be in region"); } } [Fact] public void GetCellMapWithRegion_WithExclusion_RegionExcludesExcludedCells () { LineCanvas canvas = new (); canvas.AddLine (new Point (0, 0), 10, Orientation.Horizontal, LineStyle.Single); // Exclude middle section Region exclusionRegion = new (); exclusionRegion.Combine (new Rectangle (3, 0, 4, 1), RegionOp.Union); canvas.Exclude (exclusionRegion); (Dictionary cellMap, Region region) = canvas.GetCellMapWithRegion (); Assert.NotNull (cellMap); Assert.NotEmpty (cellMap); // Excluded cells should not be in cellMap for (int x = 3; x < 7; x++) { Assert.False (cellMap.ContainsKey (new Point (x, 0)), $"({x}, 0) should be excluded from cellMap"); } // Region should match cellMap foreach (Point p in cellMap.Keys) { Assert.True (region.Contains (p.X, p.Y), $"Expected ({p.X}, {p.Y}) to be in region"); } // Excluded points should not be in region for (int x = 3; x < 7; x++) { Assert.False (region.Contains (x, 0), $"({x}, 0) should be excluded from region"); } } #endregion }