Files
Terminal.Gui/Tests/UnitTestsParallelizable/ViewBase/Draw/ViewDrawingFlowTests.cs
Tig 193a5873d1 Fixes bugs where unnecessary Draw operations were happening in LayoutAndDraw. In some cases causing everything to always be drawn.
Refactored `NeedsDraw` logic into a modular implementation in `View.NeedsDraw.cs`, introducing methods to manage drawing states more effectively. Updated `Menus.cs` event handlers to include null checks for robustness. Improved margin drawing logic in `Margin.cs` with better performance and debugging assertions.

Added comprehensive unit tests in `NeedsDrawTests.cs` and `StaticDrawTests.cs` to validate the new drawing logic, including edge cases and static `View.Draw` behavior. Removed redundant tests from `ViewDrawingFlowTests.cs`.

Refactored diagnostic flags handling in `UICatalogRunnable.cs` for clarity. Performed general code cleanup, leveraging modern C# features and improving maintainability.
2025-12-04 12:20:44 -07:00

484 lines
12 KiB
C#

#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<string> ();
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<string> ();
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
}