using System.Text; using UnitTests; using Xunit.Abstractions; namespace UnitTests_Parallelizable.ViewTests; public class ViewDrawTextAndLineCanvasTests () : FakeDriverBase { #region DrawText Tests [Fact] public void DrawText_EmptyText_DoesNotThrow () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new View { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver, Text = "" }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); var exception = Record.Exception (() => view.Draw ()); Assert.Null (exception); } [Fact] public void DrawText_NullText_DoesNotThrow () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new View { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver, Text = null! }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); var exception = Record.Exception (() => view.Draw ()); Assert.Null (exception); } [Fact] public void DrawText_DrawsTextToDriver () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new View { X = 1, Y = 1, Width = 20, Height = 20, Driver = driver, Text = "Test" }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.Draw (); // Text should appear at the content location Point screenPos = view.ContentToScreen (Point.Empty); Assert.Equal ("T", driver.Contents [screenPos.Y, screenPos.X].Grapheme); Assert.Equal ("e", driver.Contents [screenPos.Y, screenPos.X + 1].Grapheme); Assert.Equal ("s", driver.Contents [screenPos.Y, screenPos.X + 2].Grapheme); Assert.Equal ("t", driver.Contents [screenPos.Y, screenPos.X + 3].Grapheme); } [Fact] public void DrawText_WithFocus_UsesFocusAttribute () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new View { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver, Text = "Test", CanFocus = true }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.SetFocus (); view.Draw (); // Text should use focus attribute Point screenPos = view.ContentToScreen (Point.Empty); Attribute expectedAttr = view.GetAttributeForRole (VisualRole.Focus); Assert.Equal (expectedAttr, driver.Contents [screenPos.Y, screenPos.X].Attribute); } [Fact] public void DrawText_WithoutFocus_UsesNormalAttribute () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new View { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver, Text = "Test", CanFocus = true }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.Draw (); // Text should use normal attribute Point screenPos = view.ContentToScreen (Point.Empty); Attribute expectedAttr = view.GetAttributeForRole (VisualRole.Normal); Assert.Equal (expectedAttr, driver.Contents [screenPos.Y, screenPos.X].Attribute); } [Fact] public void DrawText_SetsSubViewNeedsDraw () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new View { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver, Text = "Test" }; var child = new View { X = 0, Y = 0, Width = 10, Height = 10 }; view.Add (child); view.BeginInit (); view.EndInit (); view.LayoutSubViews (); // Clear SubViewNeedsDraw view.Draw (); Assert.False (view.SubViewNeedsDraw); // Call DrawText directly which should set SubViewNeedsDraw view.DrawText (); // SubViews need to be redrawn since text was drawn over them Assert.True (view.SubViewNeedsDraw); } [Fact] public void DrawingText_Event_Raised () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); bool eventRaised = false; var view = new View { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver, Text = "Test" }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.DrawingText += (s, e) => eventRaised = true; view.Draw (); Assert.True (eventRaised); } [Fact] public void DrewText_Event_Raised () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); bool eventRaised = false; var view = new View { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver, Text = "Test" }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.DrewText += (s, e) => eventRaised = true; view.Draw (); Assert.True (eventRaised); } #endregion #region LineCanvas Tests [Fact] public void LineCanvas_InitiallyEmpty () { var view = new View (); Assert.NotNull (view.LineCanvas); Assert.Equal (Rectangle.Empty, view.LineCanvas.Bounds); } [Fact] public void RenderLineCanvas_DrawsLines () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new View { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); // Add a line to the canvas Point screenPos = new Point (15, 15); view.LineCanvas.AddLine (screenPos, 5, Orientation.Horizontal, LineStyle.Single); view.RenderLineCanvas (); // Verify the line was drawn (check for horizontal line character) for (int i = 0; i < 5; i++) { Assert.NotEqual (" ", driver.Contents [screenPos.Y, screenPos.X + i].Grapheme); } } [Fact] public void RenderLineCanvas_ClearsAfterRendering () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new View { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); // Add a line to the canvas view.LineCanvas.AddLine (new Point (15, 15), 5, Orientation.Horizontal, LineStyle.Single); Assert.NotEqual (Rectangle.Empty, view.LineCanvas.Bounds); view.RenderLineCanvas (); // LineCanvas should be cleared after rendering Assert.Equal (Rectangle.Empty, view.LineCanvas.Bounds); } [Fact] public void RenderLineCanvas_WithSuperViewRendersLineCanvas_DoesNotClear () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new View { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver, SuperViewRendersLineCanvas = true }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); // Add a line to the canvas view.LineCanvas.AddLine (new Point (15, 15), 5, Orientation.Horizontal, LineStyle.Single); Rectangle boundsBefore = view.LineCanvas.Bounds; view.RenderLineCanvas (); // LineCanvas should NOT be cleared when SuperViewRendersLineCanvas is true Assert.Equal (boundsBefore, view.LineCanvas.Bounds); } [Fact] public void SuperViewRendersLineCanvas_MergesWithParentCanvas () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var parent = new View { X = 10, Y = 10, Width = 50, Height = 50, Driver = driver }; var child = new View { X = 5, Y = 5, Width = 30, Height = 30, SuperViewRendersLineCanvas = true }; parent.Add (child); parent.BeginInit (); parent.EndInit (); parent.LayoutSubViews (); // Add a line to child's canvas child.LineCanvas.AddLine (new Point (20, 20), 5, Orientation.Horizontal, LineStyle.Single); Assert.NotEqual (Rectangle.Empty, child.LineCanvas.Bounds); Assert.Equal (Rectangle.Empty, parent.LineCanvas.Bounds); parent.Draw (); // Child's canvas should have been merged into parent's // and child's canvas should be cleared Assert.Equal (Rectangle.Empty, child.LineCanvas.Bounds); } [Fact] public void OnRenderingLineCanvas_CanPreventRendering () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new TestView { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver, PreventRenderLineCanvas = true }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); // Add a line to the canvas Point screenPos = new Point (15, 15); view.LineCanvas.AddLine (screenPos, 5, Orientation.Horizontal, LineStyle.Single); view.Draw (); // When OnRenderingLineCanvas returns true, RenderLineCanvas is not called // So the LineCanvas should still have lines (not cleared) // BUT because SuperViewRendersLineCanvas is false (default), the LineCanvas // gets cleared during the draw cycle anyway. We need to check that the // line was NOT actually rendered to the driver. bool lineRendered = true; for (int i = 0; i < 5; i++) { if (driver.Contents [screenPos.Y, screenPos.X + i].Grapheme == " ") { lineRendered = false; break; } } Assert.False (lineRendered); } #endregion #region SuperViewRendersLineCanvas Tests [Fact] public void SuperViewRendersLineCanvas_DefaultFalse () { var view = new View (); Assert.False (view.SuperViewRendersLineCanvas); } [Fact] public void SuperViewRendersLineCanvas_CanBeSet () { var view = new View { SuperViewRendersLineCanvas = true }; Assert.True (view.SuperViewRendersLineCanvas); } [Fact] public void Draw_WithSuperViewRendersLineCanvas_SetsNeedsDraw () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var parent = new View { X = 10, Y = 10, Width = 50, Height = 50, Driver = driver }; var child = new View { X = 5, Y = 5, Width = 30, Height = 30, SuperViewRendersLineCanvas = true }; parent.Add (child); parent.BeginInit (); parent.EndInit (); parent.LayoutSubViews (); // Draw once to clear NeedsDraw parent.Draw (); Assert.False (child.NeedsDraw); // Draw again - child with SuperViewRendersLineCanvas should be redrawn parent.Draw (); // The child should have been set to NeedsDraw during DrawSubViews // This is verified by the fact that it was drawn (we can't check NeedsDraw after Draw) } #endregion #region Helper Test View private class TestView : View { public bool PreventRenderLineCanvas { get; set; } protected override bool OnRenderingLineCanvas () { return PreventRenderLineCanvas || base.OnRenderingLineCanvas (); } } #endregion }