Files
Terminal.Gui/Tests/UnitTestsParallelizable/View/Draw/ViewDrawingFlowTests.cs
Tig 9a5b1c6d2c Refactor tests to decouple from global Application state
Commented out `driver ??= Application.Driver` assignments in
`DriverAssert` to prevent automatic global driver assignment.
Removed `Application.ResetState(true)` calls and commented out
state validation assertions in `GlobalTestSetup` to reduce
dependency on global state.

Reintroduced `ApplicationForceDriverTests` and
`ApplicationModelFencingTests` to validate `ForceDriver` behavior
and ensure proper handling of legacy and modern Application
models. Skipped certain `ToAnsiTests` that rely on `Application`.

Removed direct `Application.Driver` assignments in
`ViewDrawingClippingTests` and `ViewDrawingFlowTests`.
Performed general cleanup of redundant code and unused imports
to simplify the codebase.
2025-11-22 20:53:45 -07:00

699 lines
18 KiB
C#

#nullable enable
using UnitTests;
using Xunit.Abstractions;
namespace UnitTests_Parallelizable.ViewTests;
public class ViewDrawingFlowTests () : FakeDriverBase
{
#region NeedsDraw Tests
[Fact]
public void NeedsDraw_InitiallyFalse_WhenNotVisible ()
{
var view = new View { Visible = false };
view.BeginInit ();
view.EndInit ();
Assert.False (view.NeedsDraw);
}
[Fact]
public void NeedsDraw_TrueAfterSetNeedsDraw ()
{
var view = new View { X = 0, Y = 0, Width = 10, Height = 10 };
view.BeginInit ();
view.EndInit ();
view.LayoutSubViews ();
view.SetNeedsDraw ();
Assert.True (view.NeedsDraw);
}
[Fact]
public void NeedsDraw_ClearedAfterDraw ()
{
IDriver driver = CreateFakeDriver (80, 25);
driver.Clip = new Region (driver.Screen);
var view = new View
{
X = 0,
Y = 0,
Width = 10,
Height = 10,
Driver = driver
};
view.BeginInit ();
view.EndInit ();
view.LayoutSubViews ();
view.SetNeedsDraw ();
Assert.True (view.NeedsDraw);
view.Draw ();
Assert.False (view.NeedsDraw);
}
[Fact]
public void SetNeedsDraw_WithRectangle_UpdatesNeedsDrawRect ()
{
var view = new View { Driver = CreateFakeDriver (), X = 0, Y = 0, Width = 20, Height = 20 };
view.BeginInit ();
view.EndInit ();
view.LayoutSubViews ();
// After layout, view will have NeedsDrawRect set to the viewport
// We need to clear it first
view.Draw ();
Assert.False (view.NeedsDraw);
Assert.Equal (Rectangle.Empty, view.NeedsDrawRect);
var rect = new Rectangle (5, 5, 10, 10);
view.SetNeedsDraw (rect);
Assert.True (view.NeedsDraw);
Assert.Equal (rect, view.NeedsDrawRect);
}
[Fact]
public void SetNeedsDraw_MultipleRectangles_Expands ()
{
IDriver driver = CreateFakeDriver (80, 25);
driver.Clip = new Region (driver.Screen);
var view = new View { X = 0, Y = 0, Width = 30, Height = 30, Driver = driver };
view.BeginInit ();
view.EndInit ();
view.LayoutSubViews ();
// After layout, clear NeedsDraw
view.Draw ();
Assert.False (view.NeedsDraw);
view.SetNeedsDraw (new Rectangle (5, 5, 10, 10));
view.SetNeedsDraw (new Rectangle (15, 15, 10, 10));
// Should expand to cover the entire viewport when we have overlapping regions
// The current implementation expands to viewport size
Rectangle expected = new Rectangle (0, 0, 30, 30);
Assert.Equal (expected, view.NeedsDrawRect);
}
[Fact]
public void SetNeedsDraw_NotVisible_DoesNotSet ()
{
var view = new View
{
X = 0,
Y = 0,
Width = 10,
Height = 10,
Visible = false
};
view.BeginInit ();
view.EndInit ();
view.SetNeedsDraw ();
Assert.False (view.NeedsDraw);
}
[Fact]
public void SetNeedsDraw_PropagatesToSuperView ()
{
var parent = new View { X = 0, Y = 0, Width = 50, Height = 50 };
var child = new View { X = 10, Y = 10, Width = 20, Height = 20 };
parent.Add (child);
parent.BeginInit ();
parent.EndInit ();
parent.LayoutSubViews ();
child.SetNeedsDraw ();
Assert.True (child.NeedsDraw);
Assert.True (parent.SubViewNeedsDraw);
}
[Fact]
public void SetNeedsDraw_SetsAdornmentsNeedsDraw ()
{
var view = new View { X = 0, Y = 0, Width = 20, Height = 20 };
view.Border!.Thickness = new Thickness (1);
view.Padding!.Thickness = new Thickness (1);
view.BeginInit ();
view.EndInit ();
view.LayoutSubViews ();
view.SetNeedsDraw ();
Assert.True (view.Border!.NeedsDraw);
Assert.True (view.Padding!.NeedsDraw);
}
#endregion
#region SubViewNeedsDraw Tests
[Fact]
public void SubViewNeedsDraw_InitiallyFalse ()
{
IDriver driver = CreateFakeDriver (80, 25);
driver.Clip = new Region (driver.Screen);
var view = new View { Width = 10, Height = 10, Driver = driver };
view.BeginInit ();
view.EndInit ();
view.Draw (); // Draw once to clear initial NeedsDraw
Assert.False (view.SubViewNeedsDraw);
}
[Fact]
public void SetSubViewNeedsDraw_PropagatesUp ()
{
var grandparent = new View { X = 0, Y = 0, Width = 100, Height = 100 };
var parent = new View { X = 10, Y = 10, Width = 50, Height = 50 };
var child = new View { X = 5, Y = 5, Width = 20, Height = 20 };
grandparent.Add (parent);
parent.Add (child);
grandparent.BeginInit ();
grandparent.EndInit ();
grandparent.LayoutSubViews ();
child.SetSubViewNeedsDraw ();
Assert.True (child.SubViewNeedsDraw);
Assert.True (parent.SubViewNeedsDraw);
Assert.True (grandparent.SubViewNeedsDraw);
}
[Fact]
public void SubViewNeedsDraw_ClearedAfterDraw ()
{
IDriver driver = CreateFakeDriver (80, 25);
driver.Clip = new Region (driver.Screen);
var parent = new View
{
X = 0,
Y = 0,
Width = 50,
Height = 50,
Driver = driver
};
var child = new View { X = 10, Y = 10, Width = 20, Height = 20 };
parent.Add (child);
parent.BeginInit ();
parent.EndInit ();
parent.LayoutSubViews ();
child.SetNeedsDraw ();
Assert.True (parent.SubViewNeedsDraw);
parent.Draw ();
Assert.False (parent.SubViewNeedsDraw);
Assert.False (child.SubViewNeedsDraw);
}
#endregion
#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
}