#nullable enable using UnitTests; using Xunit.Abstractions; namespace ViewBaseTests.Drawing; public class ViewDrawingFlowTests () : FakeDriverBase { #region Draw Visibility Tests [Fact] public void Draw_NotVisible_DoesNotDraw () { IDriver driver = CreateFakeDriver (80, 25); var view = new View { X = 0, Y = 0, Width = 10, Height = 10, Visible = false, Driver = driver }; view.BeginInit (); view.EndInit (); view.SetNeedsDraw (); view.Draw (); // NeedsDraw should still be false (view wasn't drawn) Assert.False (view.NeedsDraw); } [Fact] public void Draw_SuperViewNotVisible_DoesNotDraw () { IDriver driver = CreateFakeDriver (80, 25); var parent = new View { X = 0, Y = 0, Width = 50, Height = 50, Visible = false, Driver = driver }; var child = new View { X = 10, Y = 10, Width = 20, Height = 20 }; parent.Add (child); parent.BeginInit (); parent.EndInit (); child.SetNeedsDraw (); child.Draw (); // Child should not have been drawn Assert.True (child.NeedsDraw); // Still needs draw } [Fact] public void Draw_Enabled_False_UsesDisabledAttribute () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); bool drawingTextCalled = false; Attribute? usedAttribute = null; var view = new TestView { X = 0, Y = 0, Width = 10, Height = 10, Enabled = false, Driver = driver }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.DrawingText += (s, e) => { drawingTextCalled = true; usedAttribute = driver.CurrentAttribute; }; view.Draw (); Assert.True (drawingTextCalled); Assert.NotNull (usedAttribute); // The disabled attribute should have been used Assert.Equal (view.GetAttributeForRole (VisualRole.Disabled), usedAttribute); } #endregion #region Draw Order Tests [Fact] public void Draw_CallsMethodsInCorrectOrder () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var callOrder = new List (); var view = new TestView { X = 0, Y = 0, Width = 20, Height = 20, Driver = driver }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.DrawingAdornmentsCallback = () => callOrder.Add ("DrawingAdornments"); view.ClearingViewportCallback = () => callOrder.Add ("ClearingViewport"); view.DrawingSubViewsCallback = () => callOrder.Add ("DrawingSubViews"); view.DrawingTextCallback = () => callOrder.Add ("DrawingText"); view.DrawingContentCallback = () => callOrder.Add ("DrawingContent"); view.RenderingLineCanvasCallback = () => callOrder.Add ("RenderingLineCanvas"); view.DrawCompleteCallback = () => callOrder.Add ("DrawComplete"); view.Draw (); Assert.Equal ( new [] { "DrawingAdornments", "ClearingViewport", "DrawingSubViews", "DrawingText", "DrawingContent", "RenderingLineCanvas", "DrawComplete" }, callOrder ); } [Fact] public void Draw_WithSubViews_DrawsInReverseOrder () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var drawOrder = new List (); var parent = new View { X = 0, Y = 0, Width = 50, Height = 50, Driver = driver }; var child1 = new TestView { X = 0, Y = 0, Width = 10, Height = 10, Id = "Child1" }; var child2 = new TestView { X = 0, Y = 10, Width = 10, Height = 10, Id = "Child2" }; var child3 = new TestView { X = 0, Y = 20, Width = 10, Height = 10, Id = "Child3" }; parent.Add (child1); parent.Add (child2); parent.Add (child3); parent.BeginInit (); parent.EndInit (); parent.LayoutSubViews (); child1.DrawingContentCallback = () => drawOrder.Add ("Child1"); child2.DrawingContentCallback = () => drawOrder.Add ("Child2"); child3.DrawingContentCallback = () => drawOrder.Add ("Child3"); parent.Draw (); // SubViews are drawn in reverse order for clipping optimization Assert.Equal (new [] { "Child3", "Child2", "Child1" }, drawOrder); } #endregion #region DrawContext Tests [Fact] public void Draw_WithContext_PassesContext () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); DrawContext? receivedContext = null; var view = new TestView { X = 0, Y = 0, Width = 20, Height = 20, Driver = driver }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.DrawingContentCallback = () => { }; view.DrawingContent += (s, e) => { receivedContext = e.DrawContext; }; var context = new DrawContext (); view.Draw (context); Assert.NotNull (receivedContext); Assert.Equal (context, receivedContext); } [Fact] public void Draw_WithoutContext_CreatesContext () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); DrawContext? receivedContext = null; var view = new TestView { X = 0, Y = 0, Width = 20, Height = 20, Driver = driver }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.DrawingContentCallback = () => { }; view.DrawingContent += (s, e) => { receivedContext = e.DrawContext; }; view.Draw (); Assert.NotNull (receivedContext); } #endregion #region Event Tests [Fact] public void ClearingViewport_CanCancel () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new View { X = 0, Y = 0, Width = 20, Height = 20, Driver = driver }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); bool clearedCalled = false; view.ClearingViewport += (s, e) => e.Cancel = true; view.ClearedViewport += (s, e) => clearedCalled = true; view.Draw (); Assert.False (clearedCalled); } [Fact] public void DrawingText_CanCancel () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var view = new View { X = 0, Y = 0, Width = 20, Height = 20, Driver = driver, Text = "Test" }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); bool drewTextCalled = false; view.DrawingText += (s, e) => e.Cancel = true; view.DrewText += (s, e) => drewTextCalled = true; view.Draw (); Assert.False (drewTextCalled); } [Fact] public void DrawingSubViews_CanCancel () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); var parent = new TestView { X = 0, Y = 0, Width = 50, Height = 50, Driver = driver }; var child = new TestView { X = 10, Y = 10, Width = 20, Height = 20 }; parent.Add (child); parent.BeginInit (); parent.EndInit (); parent.LayoutSubViews (); bool childDrawn = false; child.DrawingContentCallback = () => childDrawn = true; parent.DrawingSubViews += (s, e) => e.Cancel = true; parent.Draw (); Assert.False (childDrawn); } [Fact] public void DrawComplete_AlwaysCalled () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); bool drawCompleteCalled = false; var view = new View { X = 0, Y = 0, Width = 20, Height = 20, Driver = driver }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.DrawComplete += (s, e) => drawCompleteCalled = true; view.Draw (); Assert.True (drawCompleteCalled); } #endregion #region Transparent View Tests [Fact] public void Draw_TransparentView_DoesNotClearViewport () { IDriver driver = CreateFakeDriver (80, 25); driver.Clip = new Region (driver.Screen); bool clearedViewport = false; var view = new View { X = 0, Y = 0, Width = 20, Height = 20, Driver = driver, ViewportSettings = ViewportSettingsFlags.Transparent }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.ClearedViewport += (s, e) => clearedViewport = true; view.Draw (); Assert.False (clearedViewport); } [Fact] public void Draw_TransparentView_ExcludesDrawnRegionFromClip () { IDriver driver = CreateFakeDriver (80, 25); var initialClip = new Region (driver.Screen); driver.Clip = initialClip; var view = new View { X = 10, Y = 10, Width = 20, Height = 20, Driver = driver, ViewportSettings = ViewportSettingsFlags.Transparent }; view.BeginInit (); view.EndInit (); view.LayoutSubViews (); view.Draw (); // The drawn area should be excluded from the clip Rectangle viewportScreen = view.ViewportToScreen (view.Viewport); // Points inside the view should be excluded // Note: This test depends on the DrawContext tracking, which may not exclude if nothing was actually drawn // We're verifying the mechanism exists, not that it necessarily excludes in this specific case } #endregion #region Helper Test View private class TestView : View { public Action? DrawingAdornmentsCallback { get; set; } public Action? ClearingViewportCallback { get; set; } public Action? DrawingSubViewsCallback { get; set; } public Action? DrawingTextCallback { get; set; } public Action? DrawingContentCallback { get; set; } public Action? RenderingLineCanvasCallback { get; set; } public Action? DrawCompleteCallback { get; set; } protected override bool OnDrawingAdornments () { DrawingAdornmentsCallback?.Invoke (); return base.OnDrawingAdornments (); } protected override bool OnClearingViewport () { ClearingViewportCallback?.Invoke (); return base.OnClearingViewport (); } protected override bool OnDrawingSubViews (DrawContext? context) { DrawingSubViewsCallback?.Invoke (); return base.OnDrawingSubViews (context); } protected override bool OnDrawingText (DrawContext? context) { DrawingTextCallback?.Invoke (); return base.OnDrawingText (context); } protected override bool OnDrawingContent (DrawContext? context) { DrawingContentCallback?.Invoke (); return base.OnDrawingContent (context); } protected override bool OnRenderingLineCanvas () { RenderingLineCanvasCallback?.Invoke (); return base.OnRenderingLineCanvas (); } protected override void OnDrawComplete (DrawContext? context) { DrawCompleteCallback?.Invoke (); base.OnDrawComplete (context); } } #endregion }