Fixes #3930 - Splits tests to Tests/UnitTests, Tests/IntegrationTests, Tests/StressTests (#3954)

* Tons of API doc updates

* Removed stale test

* Removed stale tests

* Fixed Skipped Shadow test 1

* Fixed Skipped Shadow test 2

* Fixed Skipped Shadow test 3

* Removed stale test

* Removed stale test2

* Explicit unregister of event handler on Application.Driver!.ClearedContents

* Added Toplevels to dict

* code cleanup

* spelling error

* Removed stale test3

* Removed stale test4

* Removed stale test5

* added script

* tweaked script

* tweaked script

* Created StressTests project; moved some tests

* Created IntegrationTests project; moved some tests

* New yml

* made old yml just unit tests

* Tweaked Button_IsDefault_Raises_Accepted_Correctly

* tweaked script

* cleaned up ymls

* tweakled up ymls

* stress tests...

* stress tests on ubuntu only

* Fixed WindowsDriver in InvokeLeakTest

* Fixed WindowsDriver in InvokeLeakTest2

* Added Directory.Packages.props.
Added Directory.Build.props

* Shortened StressTest time

* Removed dupe file.

* DemoFiles

* Moved all tests to ./Tests dir.

* Fixed release build issue

* Fixed .sln file

* Fixed .sl* files

* Fixing ymls

* Fixing interation tests

* Create link to the file TestHelpers.

* Created Tests/UnitTestsParallelizable.
Moved all obviously parallelizable tests.
Updated yml.

* fixing logs

* fixing logs2

* fixing logs3

* don't require stress to pass for PRs

* Fix a failure?

* tweaked script

* Coudl this be it?

* Moved tons of tests to parallelizable

* Fixed some stuff

* Script to find duplicate tests

* Testing workflows

* Updated to v4

* Fix RelativeBasePath issue

* Replace powershell to pwsh

* Add ignore projects.

* Removed dupe unit tests

* Code cleanup of tests

* Cleaned up test warnings

* yml tweak

* Moved setter

* tweak ymls

* just randomly throwing spaghetti at a wall

* Enable runing 5 test runners in par

* Turned off DEBUG_DISPOSABLE for par tests

* RunningUnitTests=true

* code cleanup (forcing more Action runs)

* DISABLE_DEBUG_IDISPOSABLE

* Added View.DebugIDisposable. False by default.

* Remobed bogus tareet

* Remobed bogus tareet2

* fixed warning

* added api doc

* fixed warning

* fixed warning

* fixed warning2

* fixed warning3

* fixed warning4

---------

Co-authored-by: BDisp <bd.bdisp@gmail.com>
This commit is contained in:
Tig
2025-03-05 23:44:27 -07:00
committed by GitHub
parent 64b216b1e8
commit b0f32811eb
303 changed files with 11531 additions and 11883 deletions

View File

@@ -0,0 +1,68 @@
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class AdornmentSubViewTests (ITestOutputHelper output)
{
private readonly ITestOutputHelper _output = output;
[Theory]
[InlineData (0, 0, false)] // Margin has no thickness, so false
[InlineData (0, 1, false)] // Margin has no thickness, so false
[InlineData (1, 0, true)]
[InlineData (1, 1, true)]
[InlineData (2, 1, true)]
public void Adornment_WithSubView_GetViewsUnderMouse_Finds (int viewMargin, int subViewMargin, bool expectedFound)
{
Application.Top = new Toplevel()
{
Width = 10,
Height = 10
};
Application.Top.Margin.Thickness = new Thickness (viewMargin);
var subView = new View ()
{
X = 0,
Y = 0,
Width = 5,
Height = 5
};
subView.Margin.Thickness = new Thickness (subViewMargin);
Application.Top.Margin.Add (subView);
Application.Top.Layout ();
var foundView = View.GetViewsUnderMouse (new Point(0, 0)).LastOrDefault ();
bool found = foundView == subView || foundView == subView.Margin;
Assert.Equal (expectedFound, found);
Application.Top.Dispose ();
Application.ResetState (ignoreDisposed: true);
}
[Fact]
public void Adornment_WithNonVisibleSubView_GetViewsUnderMouse_Finds_Adornment ()
{
Application.Top = new Toplevel ()
{
Width = 10,
Height = 10
};
Application.Top.Padding.Thickness = new Thickness (1);
var subView = new View ()
{
X = 0,
Y = 0,
Width = 1,
Height = 1,
Visible = false
};
Application.Top.Padding.Add (subView);
Application.Top.Layout ();
Assert.Equal (Application.Top.Padding, View.GetViewsUnderMouse (new Point(0, 0)).LastOrDefault ());
Application.Top?.Dispose ();
Application.ResetState (ignoreDisposed: true);
}
}

View File

@@ -0,0 +1,73 @@
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class AdornmentTests (ITestOutputHelper output)
{
[Fact]
[SetupFakeDriver]
public void Border_Is_Cleared_After_Margin_Thickness_Change ()
{
View view = new () { Text = "View", Width = 6, Height = 3, BorderStyle = LineStyle.Rounded };
// Remove border bottom thickness
view.Border!.Thickness = new (1, 1, 1, 0);
// Add margin bottom thickness
view.Margin!.Thickness = new (0, 0, 0, 1);
Assert.Equal (6, view.Width);
Assert.Equal (3, view.Height);
view.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
╭────╮
│View│
",
output
);
// Add border bottom thickness
view.Border!.Thickness = new (1, 1, 1, 1);
// Remove margin bottom thickness
view.Margin!.Thickness = new (0, 0, 0, 0);
view.Draw ();
Assert.Equal (6, view.Width);
Assert.Equal (3, view.Height);
DriverAssert.AssertDriverContentsWithFrameAre (
@"
╭────╮
│View│
╰────╯
",
output
);
// Remove border bottom thickness
view.Border!.Thickness = new (1, 1, 1, 0);
// Add margin bottom thickness
view.Margin!.Thickness = new (0, 0, 0, 1);
Assert.Equal (6, view.Width);
Assert.Equal (3, view.Height);
View.SetClipToScreen ();
view.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
╭────╮
│View│
",
output
);
}
}

View File

@@ -0,0 +1,947 @@
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class BorderTests (ITestOutputHelper output)
{
[Fact]
[SetupFakeDriver]
public void Border_Parent_HasFocus_Title_Uses_FocusAttribute ()
{
var superView = new View { Width = 10, Height = 10, CanFocus = true };
var otherView = new View { Width = 0, Height = 0, CanFocus = true };
superView.Add (otherView);
var view = new View { Title = "A", Height = 2, Width = 5 };
superView.Add (view);
view.Border.Thickness = new (0, 1, 0, 0);
view.Border.LineStyle = LineStyle.Single;
view.ColorScheme = new ()
{
Normal = new (Color.Red, Color.Green),
Focus = new (Color.Green, Color.Red)
};
Assert.NotEqual (view.ColorScheme.Normal.Foreground, view.ColorScheme.Focus.Foreground);
Assert.Equal (ColorName16.Red, view.Border.GetNormalColor ().Foreground.GetClosestNamedColor16 ());
Assert.Equal (ColorName16.Green, view.Border.GetFocusColor ().Foreground.GetClosestNamedColor16 ());
Assert.Equal (view.GetFocusColor (), view.Border.GetFocusColor ());
superView.BeginInit ();
superView.EndInit ();
superView.Draw ();
var expected = @"─┤A├─";
DriverAssert.AssertDriverContentsAre (expected, output);
DriverAssert.AssertDriverAttributesAre ("00000", output, null, view.ColorScheme.Normal);
view.CanFocus = true;
view.SetFocus ();
View.SetClipToScreen ();
view.Draw ();
Assert.Equal (view.GetFocusColor (), view.Border.GetFocusColor ());
Assert.Equal (view.ColorScheme.Focus.Foreground, view.Border.GetFocusColor ().Foreground);
Assert.Equal (view.ColorScheme.Normal.Foreground, view.Border.GetNormalColor ().Foreground);
DriverAssert.AssertDriverAttributesAre ("00100", output, null, view.ColorScheme.Normal, view.GetFocusColor ());
}
[Fact]
[SetupFakeDriver]
public void Border_Uses_Parent_ColorScheme ()
{
var view = new View { Title = "A", Height = 2, Width = 5 };
view.Border.Thickness = new (0, 1, 0, 0);
view.Border.LineStyle = LineStyle.Single;
view.ColorScheme = new ()
{
Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red)
};
Assert.Equal (ColorName16.Red, view.Border.GetNormalColor ().Foreground.GetClosestNamedColor16 ());
Assert.Equal (ColorName16.Green, view.Border.GetFocusColor ().Foreground.GetClosestNamedColor16 ());
Assert.Equal (view.GetNormalColor (), view.Border.GetNormalColor ());
Assert.Equal (view.GetFocusColor (), view.Border.GetFocusColor ());
view.BeginInit ();
view.EndInit ();
view.Draw ();
var expected = @"─┤A├─";
DriverAssert.AssertDriverContentsAre (expected, output);
DriverAssert.AssertDriverAttributesAre ("00000", output, null, view.ColorScheme.Normal);
}
[Theory]
[AutoInitShutdown]
[InlineData (0)]
[InlineData (1)]
[InlineData (2)]
[InlineData (3)]
[InlineData (4)]
[InlineData (5)]
[InlineData (6)]
[InlineData (7)]
[InlineData (8)]
[InlineData (9)]
[InlineData (10)]
public void Border_With_Title_Border_Double_Thickness_Top_Four_Size_Width (int width)
{
var win = new Window
{
Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double
};
win.Border.Thickness = win.Border.Thickness with { Top = 4 };
RunState rs = Application.Begin (win);
var firstIteration = false;
((FakeDriver)Application.Driver!).SetBufferSize (width, 5);
Application.RunIteration (ref rs, firstIteration);
var expected = string.Empty;
switch (width)
{
case 1:
Assert.Equal (new (0, 0, 1, 5), win.Frame);
expected = @"
║";
break;
case 2:
Assert.Equal (new (0, 0, 2, 5), win.Frame);
expected = @"
╔╗
║║
╚╝";
break;
case 3:
Assert.Equal (new (0, 0, 3, 5), win.Frame);
expected = @"
╔═╗
║ ║
╚═╝";
break;
case 4:
Assert.Equal (new (0, 0, 4, 5), win.Frame);
expected = @"
╒╕
╔╡╞╗
║╘╛║
╚══╝";
break;
case 5:
Assert.Equal (new (0, 0, 5, 5), win.Frame);
expected = @"
╒═╕
╔╡1╞╗
║╘═╛║
╚═══╝";
break;
case 6:
Assert.Equal (new (0, 0, 6, 5), win.Frame);
expected = @"
╒══╕
╔╡12╞╗
║╘══╛║
╚════╝";
break;
case 7:
Assert.Equal (new (0, 0, 7, 5), win.Frame);
expected = @"
╒═══╕
╔╡123╞╗
║╘═══╛║
╚═════╝";
break;
case 8:
Assert.Equal (new (0, 0, 8, 5), win.Frame);
expected = @"
╒════╕
╔╡1234╞╗
║╘════╛║
╚══════╝";
break;
case 9:
Assert.Equal (new (0, 0, 9, 5), win.Frame);
expected = @"
╒════╕
╔╡1234╞═╗
║╘════╛ ║
╚═══════╝";
break;
case 10:
Assert.Equal (new (0, 0, 10, 5), win.Frame);
expected = @"
╒════╕
╔╡1234╞══╗
║╘════╛ ║
╚════════╝";
break;
}
_ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
Application.End (rs);
win.Dispose ();
}
[Theory]
[AutoInitShutdown]
[InlineData (0)]
[InlineData (1)]
[InlineData (2)]
[InlineData (3)]
[InlineData (4)]
[InlineData (5)]
[InlineData (6)]
[InlineData (7)]
[InlineData (8)]
[InlineData (9)]
[InlineData (10)]
public void Border_With_Title_Border_Double_Thickness_Top_Three_Size_Width (int width)
{
var win = new Window
{
Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double
};
win.Border.Thickness = win.Border.Thickness with { Top = 3 };
RunState rs = Application.Begin (win);
((FakeDriver)Application.Driver!).SetBufferSize (width, 4);
Application.RunIteration (ref rs, false);
var expected = string.Empty;
switch (width)
{
case 1:
Assert.Equal (new (0, 0, 1, 4), win.Frame);
expected = @"
║";
break;
case 2:
Assert.Equal (new (0, 0, 2, 4), win.Frame);
expected = @"
╔╗
║║
╚╝";
break;
case 3:
Assert.Equal (new (0, 0, 3, 4), win.Frame);
expected = @"
╔═╗
║ ║
╚═╝";
break;
case 4:
Assert.Equal (new (0, 0, 4, 4), win.Frame);
expected = @"
╒╕
╔╡╞╗
║╘╛║
╚══╝";
break;
case 5:
Assert.Equal (new (0, 0, 5, 4), win.Frame);
expected = @"
╒═╕
╔╡1╞╗
║╘═╛║
╚═══╝";
break;
case 6:
Assert.Equal (new (0, 0, 6, 4), win.Frame);
expected = @"
╒══╕
╔╡12╞╗
║╘══╛║
╚════╝";
break;
case 7:
Assert.Equal (new (0, 0, 7, 4), win.Frame);
expected = @"
╒═══╕
╔╡123╞╗
║╘═══╛║
╚═════╝";
break;
case 8:
Assert.Equal (new (0, 0, 8, 4), win.Frame);
expected = @"
╒════╕
╔╡1234╞╗
║╘════╛║
╚══════╝";
break;
case 9:
Assert.Equal (new (0, 0, 9, 4), win.Frame);
expected = @"
╒════╕
╔╡1234╞═╗
║╘════╛ ║
╚═══════╝";
break;
case 10:
Assert.Equal (new (0, 0, 10, 4), win.Frame);
expected = @"
╒════╕
╔╡1234╞══╗
║╘════╛ ║
╚════════╝";
break;
}
_ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
Application.End (rs);
win.Dispose ();
}
[Theory]
[AutoInitShutdown]
[InlineData (0)]
[InlineData (1)]
[InlineData (2)]
[InlineData (3)]
[InlineData (4)]
[InlineData (5)]
[InlineData (6)]
[InlineData (7)]
[InlineData (8)]
[InlineData (9)]
[InlineData (10)]
public void Border_With_Title_Border_Double_Thickness_Top_Two_Size_Width (int width)
{
var win = new Window
{
Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double
};
win.Border.Thickness = win.Border.Thickness with { Top = 2 };
RunState rs = Application.Begin (win);
var firstIteration = false;
((FakeDriver)Application.Driver!).SetBufferSize (width, 4);
Application.RunIteration (ref rs, firstIteration);
var expected = string.Empty;
switch (width)
{
case 1:
Assert.Equal (new (0, 0, 1, 4), win.Frame);
expected = @"
║";
break;
case 2:
Assert.Equal (new (0, 0, 2, 4), win.Frame);
expected = @"
╔╗
║║
╚╝";
break;
case 3:
Assert.Equal (new (0, 0, 3, 4), win.Frame);
expected = @"
╔═╗
║ ║
╚═╝";
break;
case 4:
Assert.Equal (new (0, 0, 4, 4), win.Frame);
expected = @"
╒╕
╔╛╘╗
║ ║
╚══╝";
break;
case 5:
Assert.Equal (new (0, 0, 5, 4), win.Frame);
expected = @"
╒═╕
╔╛1╘╗
║ ║
╚═══╝";
break;
case 6:
Assert.Equal (new (0, 0, 6, 4), win.Frame);
expected = @"
╒══╕
╔╛12╘╗
║ ║
╚════╝";
break;
case 7:
Assert.Equal (new (0, 0, 7, 4), win.Frame);
expected = @"
╒═══╕
╔╛123╘╗
║ ║
╚═════╝";
break;
case 8:
Assert.Equal (new (0, 0, 8, 4), win.Frame);
expected = @"
╒════╕
╔╛1234╘╗
║ ║
╚══════╝";
break;
case 9:
Assert.Equal (new (0, 0, 9, 4), win.Frame);
expected = @"
╒════╕
╔╛1234╘═╗
║ ║
╚═══════╝";
break;
case 10:
Assert.Equal (new (0, 0, 10, 4), win.Frame);
expected = @"
╒════╕
╔╛1234╘══╗
║ ║
╚════════╝";
break;
}
_ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
Application.End (rs);
win.Dispose ();
}
[Theory]
[AutoInitShutdown]
[InlineData (0)]
[InlineData (1)]
[InlineData (2)]
[InlineData (3)]
public void Border_With_Title_Size_Height (int height)
{
var win = new Window { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill () };
RunState rs = Application.Begin (win);
var firstIteration = false;
((FakeDriver)Application.Driver!).SetBufferSize (20, height);
Application.RunIteration (ref rs, firstIteration);
var expected = string.Empty;
switch (height)
{
case 0:
//Assert.Equal (new (0, 0, 17, 0), subview.Frame);
expected = @"
";
break;
case 1:
//Assert.Equal (new (0, 0, 17, 0), subview.Frame);
expected = @"
─┤1234├─────────────";
break;
case 2:
//Assert.Equal (new (0, 0, 17, 1), subview.Frame);
expected = @"
┌┤1234├────────────┐
└──────────────────┘
";
break;
case 3:
//Assert.Equal (new (0, 0, 17, 2), subview.Frame);
expected = @"
┌┤1234├────────────┐
│ │
└──────────────────┘
";
break;
}
_ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
Application.End (rs);
win.Dispose ();
}
[Theory]
[AutoInitShutdown]
[InlineData (0)]
[InlineData (1)]
[InlineData (2)]
[InlineData (3)]
[InlineData (4)]
[InlineData (5)]
[InlineData (6)]
[InlineData (7)]
[InlineData (8)]
[InlineData (9)]
[InlineData (10)]
public void Border_With_Title_Size_Width (int width)
{
var win = new Window { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill () };
RunState rs = Application.Begin (win);
var firstIteration = false;
((FakeDriver)Application.Driver!).SetBufferSize (width, 3);
Application.RunIteration (ref rs, firstIteration);
var expected = string.Empty;
switch (width)
{
case 1:
//Assert.Equal (new (0, 0, 17, 0), subview.Frame);
expected = @"
│";
break;
case 2:
//Assert.Equal (new (0, 0, 17, 1), subview.Frame);
expected = @"
┌┐
││
└┘";
break;
case 3:
//Assert.Equal (new (0, 0, 17, 2), subview.Frame);
expected = @"
┌─┐
│ │
└─┘
";
break;
case 4:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌┤├┐
│ │
└──┘";
break;
case 5:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌┤1├┐
│ │
└───┘";
break;
case 6:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌┤12├┐
│ │
└────┘";
break;
case 7:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌┤123├┐
│ │
└─────┘";
break;
case 8:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌┤1234├┐
│ │
└──────┘";
break;
case 9:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌┤1234├─┐
│ │
└───────┘";
break;
case 10:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌┤1234├──┐
│ │
└────────┘";
break;
}
_ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
win.Dispose ();
}
[Theory]
[InlineData (0, 0, 0, 2, 2)]
[InlineData (1, 0, 0, 4, 4)]
[InlineData (2, 0, 0, 6, 6)]
[InlineData (1, 1, 0, 5, 4)]
[InlineData (1, 0, 1, 4, 5)]
[InlineData (1, 1, 1, 5, 5)]
[InlineData (1, 10, 10, 14, 14)]
public void FrameToScreen_NestedSuperView_WithBorder (
int superOffset,
int frameX,
int frameY,
int expectedScreenX,
int expectedScreenY
)
{
var superSuper = new View
{
X = superOffset,
Y = superOffset,
Width = 30,
Height = 30,
BorderStyle = LineStyle.Single
};
var super = new View
{
X = superOffset,
Y = superOffset,
Width = 20,
Height = 20,
BorderStyle = LineStyle.Single
};
superSuper.Add (super);
var view = new View { X = frameX, Y = frameY, Width = 10, Height = 10 };
super.Add (view);
superSuper.Layout ();
var expected = new Rectangle (expectedScreenX, expectedScreenY, 10, 10);
Rectangle actual = view.FrameToScreen ();
Assert.Equal (expected, actual);
}
[Theory]
[InlineData (0, 0, 0, 1, 1)]
[InlineData (1, 0, 0, 2, 2)]
[InlineData (2, 0, 0, 3, 3)]
[InlineData (1, 1, 0, 3, 2)]
[InlineData (1, 0, 1, 2, 3)]
[InlineData (1, 1, 1, 3, 3)]
[InlineData (1, 10, 10, 12, 12)]
public void FrameToScreen_SuperView_WithBorder (
int superOffset,
int frameX,
int frameY,
int expectedScreenX,
int expectedScreenY
)
{
var super = new View
{
X = superOffset,
Y = superOffset,
Width = 20,
Height = 20,
BorderStyle = LineStyle.Single
};
var view = new View { X = frameX, Y = frameY, Width = 10, Height = 10 };
super.Add (view);
super.Layout ();
var expected = new Rectangle (expectedScreenX, expectedScreenY, 10, 10);
Rectangle actual = view.FrameToScreen ();
Assert.Equal (expected, actual);
}
[Fact]
[AutoInitShutdown]
public void HasSuperView ()
{
var top = new Toplevel ();
top.BorderStyle = LineStyle.Double;
var frame = new FrameView { Width = Dim.Fill (), Height = Dim.Fill () };
top.Add (frame);
RunState rs = Application.Begin (top);
var firstIteration = false;
((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
Application.RunIteration (ref rs, firstIteration);
var expected = @"
╔═══╗
║┌─┐║
║│ │║
║└─┘║
╚═══╝";
_ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
Application.End (rs);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void HasSuperView_Title ()
{
var top = new Toplevel ();
top.BorderStyle = LineStyle.Double;
var frame = new FrameView { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill () };
top.Add (frame);
RunState rs = Application.Begin (top);
var firstIteration = false;
((FakeDriver)Application.Driver!).SetBufferSize (10, 4);
Application.RunIteration (ref rs, firstIteration);
var expected = @"
╔════════╗
║┌┤1234├┐║
║└──────┘║
╚════════╝";
_ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
Application.End (rs);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void NoSuperView ()
{
var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () };
RunState rs = Application.Begin (win);
var firstIteration = false;
((FakeDriver)Application.Driver!).SetBufferSize (3, 3);
Application.RunIteration (ref rs, firstIteration);
var expected = @"
┌─┐
│ │
└─┘";
_ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
win.Dispose ();
}
[Fact]
public void View_BorderStyle_Defaults ()
{
var view = new View ();
Assert.Equal (LineStyle.None, view.BorderStyle);
Assert.Equal (Thickness.Empty, view.Border.Thickness);
view.Dispose ();
}
[Fact]
public void View_SetBorderStyle ()
{
var view = new View ();
view.BorderStyle = LineStyle.Single;
Assert.Equal (LineStyle.Single, view.BorderStyle);
Assert.Equal (new (1), view.Border.Thickness);
view.BorderStyle = LineStyle.Double;
Assert.Equal (LineStyle.Double, view.BorderStyle);
Assert.Equal (new (1), view.Border.Thickness);
view.BorderStyle = LineStyle.None;
Assert.Equal (LineStyle.None, view.BorderStyle);
Assert.Equal (Thickness.Empty, view.Border.Thickness);
view.Dispose ();
}
[Theory]
[InlineData (false, @"
┌───┐
│ ║ │
│═┌┄│
│ ┊ │
└───┘")]
[InlineData (true, @"
╔═╦─┐
║ ║ │
╠═╬┄┤
│ ┊ ┊
└─┴┄┘")]
[SetupFakeDriver]
public void SuperViewRendersLineCanvas_No_Subviews_AutoJoinsLines (bool superViewRendersLineCanvas, string expected)
{
View superView = new View ()
{
Id = "superView",
Width = 5,
Height = 5,
BorderStyle = LineStyle.Single
};
View view1 = new View ()
{
Id = "view1",
Width = 3,
Height = 3,
X = -1,
Y = -1,
BorderStyle = LineStyle.Double,
SuperViewRendersLineCanvas = superViewRendersLineCanvas
};
View view2 = new View ()
{
Id = "view2",
Width = 3,
Height = 3,
X = 1,
Y = 1,
BorderStyle = LineStyle.Dotted,
SuperViewRendersLineCanvas = superViewRendersLineCanvas
};
superView.Add (view1, view2);
superView.BeginInit ();
superView.EndInit ();
superView.Draw ();
DriverAssert.AssertDriverContentsAre (expected, output);
}
[Theory]
[InlineData (false, @"
┌┤A├──────┐
│ ║ │
│ ║ │
│════┌┤C├┄│
│ ┊ │
│ ┊ │
└─────────┘")]
[InlineData (true, @"
╔╡A╞═╦────┐
║ ║ │
║ ║ │
╠════╬┤C├┄┤
│ ┊ ┊
│ ┊ ┊
└────┴┄┄┄┄┘")]
[SetupFakeDriver]
public void SuperViewRendersLineCanvas_Title_AutoJoinsLines (bool superViewRendersLineCanvas, string expected)
{
View superView = new View ()
{
Id = "superView",
Title = "A",
Width = 11,
Height = 7,
CanFocus = true,
BorderStyle = LineStyle.Single,
};
View view1 = new View ()
{
Id = "view1",
Title = "B",
Width = 6,
Height = 4,
X = -1,
Y = -1,
CanFocus = true,
BorderStyle = LineStyle.Double,
SuperViewRendersLineCanvas = superViewRendersLineCanvas
};
View view2 = new View ()
{
Id = "view2",
Title = "C",
Width = 6,
Height = 4,
X = 4,
Y = 2,
CanFocus = true,
BorderStyle = LineStyle.Dotted,
SuperViewRendersLineCanvas = superViewRendersLineCanvas
};
superView.Add (view1, view2);
superView.BeginInit ();
superView.EndInit ();
superView.Draw ();
DriverAssert.AssertDriverContentsAre (expected, output);
}
}

View File

@@ -0,0 +1,46 @@
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class MarginTests (ITestOutputHelper output)
{
[Fact]
[SetupFakeDriver]
public void Margin_Is_Transparent ()
{
((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
var view = new View { Height = 3, Width = 3 };
view.Margin.Diagnostics = ViewDiagnosticFlags.Thickness;
view.Margin.Thickness = new (1);
Application.Top = new Toplevel ();
Application.TopLevels.Push (Gui.Application.Top);
Application.Top.ColorScheme = new()
{
Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red)
};
Application.Top.Add (view);
Assert.Equal (ColorName16.Red, view.Margin.GetNormalColor ().Foreground.GetClosestNamedColor16 ());
Assert.Equal (ColorName16.Red, Application.Top.GetNormalColor ().Foreground.GetClosestNamedColor16 ());
Application.Top.BeginInit ();
Application.Top.EndInit ();
Application.LayoutAndDraw();
DriverAssert.AssertDriverContentsAre (
@"
MMM
M M
MMM",
output
);
DriverAssert.AssertDriverAttributesAre ("0", output, null, Application.Top.GetNormalColor ());
Application.ResetState (true);
}
}

View File

@@ -0,0 +1,39 @@
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class PaddingTests (ITestOutputHelper output)
{
[Fact]
[SetupFakeDriver]
public void Padding_Uses_Parent_ColorScheme ()
{
((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
var view = new View { Height = 3, Width = 3 };
view.Padding.Thickness = new (1);
view.Padding.Diagnostics = ViewDiagnosticFlags.Thickness;
view.ColorScheme = new()
{
Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red)
};
Assert.Equal (ColorName16.Red, view.Padding.GetNormalColor ().Foreground.GetClosestNamedColor16 ());
Assert.Equal (view.GetNormalColor (), view.Padding.GetNormalColor ());
view.BeginInit ();
view.EndInit ();
view.Draw ();
DriverAssert.AssertDriverContentsAre (
@"
PPP
P P
PPP",
output
);
DriverAssert.AssertDriverAttributesAre ("0", output, null, view.GetNormalColor ());
}
}

View File

@@ -0,0 +1,127 @@
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class ShadowStyleTests (ITestOutputHelper output)
{
[Theory]
[InlineData (
ShadowStyle.None,
"""
011
111
111
""")]
[InlineData (
ShadowStyle.Transparent,
"""
031
131
111
""")]
[InlineData (
ShadowStyle.Opaque,
"""
021
221
111
""")]
[SetupFakeDriver]
public void ShadowView_Colors (ShadowStyle style, string expectedAttrs)
{
((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
Color fg = Color.Red;
Color bg = Color.Green;
// 0 - View
// 1 - SuperView
// 2 - Opaque - fg is Black, bg is SuperView.Bg
// 3 - Transparent - fg is darker fg, bg is darker bg
Attribute [] attributes =
{
Attribute.Default,
new (fg, bg),
new (Color.Black, bg),
new (fg.GetDarkerColor (), bg.GetDarkerColor ())
};
var superView = new Toplevel
{
Height = 3,
Width = 3,
Text = "012ABC!@#",
ColorScheme = new (new Attribute (fg, bg))
};
superView.TextFormatter.WordWrap = true;
View view = new ()
{
Width = Dim.Auto (),
Height = Dim.Auto (),
Text = "*",
ShadowStyle = style,
ColorScheme = new (Attribute.Default)
};
superView.Add (view);
Application.TopLevels.Push (superView);
Application.LayoutAndDraw (true);
DriverAssert.AssertDriverAttributesAre (expectedAttrs, output, Application.Driver, attributes);
Application.ResetState (true);
}
// Visual tests
[Theory]
[InlineData (
ShadowStyle.None,
"""
01#$
AB#$
!@#$
!@#$
""")]
[InlineData (
ShadowStyle.Opaque,
"""
01▖$
AB▌$
▝▀▘$
!@#$
""")]
[InlineData (
ShadowStyle.Transparent,
"""
01#$
AB#$
!@#$
!@#$
""")]
[SetupFakeDriver]
public void Visual_Test (ShadowStyle style, string expected)
{
((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
var superView = new Toplevel
{
Width = 4,
Height = 4,
Text = "!@#$".Repeat (4)!
};
superView.TextFormatter.WordWrap = true;
var view = new View
{
Text = "01\nAB",
Width = Dim.Auto (),
Height = Dim.Auto ()
};
view.ShadowStyle = style;
superView.Add (view);
Application.TopLevels.Push (superView);
Application.LayoutAndDraw (true);
DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
view.Dispose ();
Application.ResetState (true);
}
}

View File

@@ -0,0 +1,17 @@
using System.Text;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class ArrangementTests (ITestOutputHelper output)
{
private readonly ITestOutputHelper _output = output;
// Test that TopResizable and Movable are mutually exclusive and Movable wins
[Fact]
public void TopResizableAndMovableMutuallyExclusive ()
{
// TODO: Write test.
}
}

View File

@@ -0,0 +1,36 @@
#nullable enable
using System.Text;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
[Trait ("Category", "Output")]
public class ColorSchemeTests (ITestOutputHelper _output)
{
[Fact]
public void GetHotNormalColor_ColorScheme ()
{
var view = new View { ColorScheme = Colors.ColorSchemes ["Base"] };
Assert.Equal (view.ColorScheme.HotNormal, view.GetHotNormalColor ());
view.Enabled = false;
Assert.Equal (view.ColorScheme.Disabled, view.GetHotNormalColor ());
view.Dispose ();
}
[Fact]
public void GetNormalColor_ColorScheme ()
{
var view = new View { ColorScheme = Colors.ColorSchemes ["Base"] };
Assert.Equal (view.ColorScheme.Normal, view.GetNormalColor ());
view.Enabled = false;
Assert.Equal (view.ColorScheme.Disabled, view.GetNormalColor ());
view.Dispose ();
}
}

View File

@@ -0,0 +1,30 @@
#nullable enable
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
/// <summary>
/// Tests <see cref="View.Diagnostics"/> static property and <see cref="ViewDiagnosticFlags"/> enum.
/// </summary>
/// <param name="output"></param>
[Trait ("Category", "Output")]
public class DiagnosticTests ()
{
/// <summary>
/// /// Tests <see cref="View.Diagnostics"/> static property and <see cref="ViewDiagnosticFlags"/> enum.
/// ///
/// </summary>
[Fact]
public void Diagnostics_Sets ()
{
// View.Diagnostics is a static property that returns the current diagnostic flags.
Assert.Equal (ViewDiagnosticFlags.Off, View.Diagnostics);
// View.Diagnostics can be set to a new value.
View.Diagnostics = ViewDiagnosticFlags.Thickness;
Assert.Equal (ViewDiagnosticFlags.Thickness, View.Diagnostics);
// Ensure we turn off at the end of the test
View.Diagnostics = ViewDiagnosticFlags.Off;
}
}

View File

@@ -0,0 +1,60 @@
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.LayoutTests;
public class AllViewsDrawTests (ITestOutputHelper _output) : TestsAllViews
{
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_Draw_Does_Not_Layout (Type viewType)
{
Application.ResetState (true);
// Required for spinner view that wants to register timeouts
Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
var view = (View)CreateInstanceIfNotGeneric (viewType);
if (view == null)
{
_output.WriteLine ($"Ignoring {viewType} - It's a Generic");
return;
}
_output.WriteLine ($"Testing {viewType}");
if (view is IDesignable designable)
{
designable.EnableForDesign ();
}
var drawCompleteCount = 0;
view.DrawComplete += (s, e) => drawCompleteCount++;
var layoutStartedCount = 0;
view.SubviewLayout += (s, e) => layoutStartedCount++;
var layoutCompleteCount = 0;
view.SubviewsLaidOut += (s, e) => layoutCompleteCount++;
view.SetNeedsLayout ();
view.Layout ();
Assert.Equal (0, drawCompleteCount);
Assert.Equal (1, layoutStartedCount);
Assert.Equal (1, layoutCompleteCount);
if (view.Visible)
{
view.SetNeedsDraw ();
view.Draw ();
Assert.Equal (1, drawCompleteCount);
Assert.Equal (1, layoutStartedCount);
Assert.Equal (1, layoutCompleteCount);
}
}
}

View File

@@ -0,0 +1,413 @@
#nullable enable
using Moq;
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
[Trait ("Category", "Output")]
public class ClearViewportTests (ITestOutputHelper _output)
{
public class TestableView : View
{
public bool TestOnClearingViewport () { return OnClearingViewport (); }
public int OnClearingViewportCalled { get; set; } = 0;
public bool CancelOnClearingViewport { get; set; }
protected override bool OnClearingViewport ()
{
OnClearingViewportCalled++;
return CancelOnClearingViewport;
}
public int OnClearedViewportCalled { get; set; } = 0;
protected override void OnClearedViewport ()
{
OnClearedViewportCalled++;
}
}
[Fact]
public void DoClearViewport_ViewportIsTransparent_DoesNotClear ()
{
// Arrange
Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
view.Object.ViewportSettings = ViewportSettings.Transparent;
// Act
view.Object.DoClearViewport ();
// Assert
Assert.Equal (0, view.Object.OnClearingViewportCalled);
Assert.Equal (0, view.Object.OnClearedViewportCalled);
}
[Fact]
public void DoClearViewport_OnClearingViewportReturnsTrue_DoesNotClear ()
{
// Arrange
Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
view.Object.CancelOnClearingViewport = true;
// Act
view.Object.DoClearViewport ();
// Assert
Assert.Equal (0, view.Object.OnClearedViewportCalled);
}
[Fact]
public void DoClearViewport_ClearingViewportEventCancelled_DoesNotClear ()
{
// Arrange
Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
view.Object.ClearingViewport += (sender, e) => e.Cancel = true;
// Act
view.Object.DoClearViewport ();
// Assert
Assert.Equal (0, view.Object.OnClearedViewportCalled);
}
[Fact]
public void DoClearViewport_ClearsViewport ()
{
// Arrange
Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
// Act
view.Object.DoClearViewport ();
// Assert
Assert.Equal (1, view.Object.OnClearedViewportCalled);
}
[Fact]
public void DoClearViewport_RaisesClearingViewportEvent ()
{
// Arrange
Mock<TestableView> view = new Mock<TestableView> { CallBase = true };
var eventRaised = false;
view.Object.ClearingViewport += (sender, e) => eventRaised = true;
// Act
view.Object.DoClearViewport ();
// Assert
Assert.True (eventRaised);
}
[Fact]
[SetupFakeDriver]
public void Clear_ClearsEntireViewport ()
{
var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
var view = new View
{
Text = "X",
X = 1, Y = 1,
Width = 3, Height = 3,
BorderStyle = LineStyle.Single
};
superView.Add (view);
superView.BeginInit ();
superView.EndInit ();
superView.LayoutSubviews ();
superView.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
// On Draw exit the view is excluded from the clip, so this will do nothing.
view.ClearViewport ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
View.SetClipToScreen ();
view.ClearViewport ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
}
[Fact]
[SetupFakeDriver]
public void Clear_WithClearVisibleContentOnly_ClearsVisibleContentOnly ()
{
var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
var view = new View
{
Text = "X",
X = 1, Y = 1,
Width = 3, Height = 3,
BorderStyle = LineStyle.Single,
ViewportSettings = ViewportSettings.ClearContentOnly
};
superView.Add (view);
superView.BeginInit ();
superView.EndInit ();
superView.LayoutSubviews ();
superView.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
View.SetClipToScreen ();
view.ClearViewport ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
}
[Fact]
[AutoInitShutdown]
public void Clear_Viewport_Can_Use_Driver_AddRune_Or_AddStr_Methods ()
{
var view = new FrameView { Width = Dim.Fill (), Height = Dim.Fill () };
view.DrawingContent += (s, e) =>
{
Region savedClip = view.AddViewportToClip ();
for (var row = 0; row < view.Viewport.Height; row++)
{
Application.Driver?.Move (1, row + 1);
for (var col = 0; col < view.Viewport.Width; col++)
{
Application.Driver?.AddStr ($"{col}");
}
}
View.SetClip (savedClip);
e.Cancel = true;
};
var top = new Toplevel ();
top.Add (view);
Application.Begin (top);
((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
var expected = @"
┌──────────────────┐
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
└──────────────────┘
"
;
Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, _output);
Assert.Equal (new (0, 0, 20, 10), pos);
view.FillRect (view.Viewport);
expected = @"
┌──────────────────┐
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────┘
"
;
pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, _output);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Clear_Can_Use_Driver_AddRune_Or_AddStr_Methods ()
{
var view = new FrameView { Width = Dim.Fill (), Height = Dim.Fill () };
view.DrawingContent += (s, e) =>
{
Region savedClip = view.AddViewportToClip ();
for (var row = 0; row < view.Viewport.Height; row++)
{
Application.Driver?.Move (1, row + 1);
for (var col = 0; col < view.Viewport.Width; col++)
{
Application.Driver?.AddStr ($"{col}");
}
}
View.SetClip (savedClip);
e.Cancel = true;
};
var top = new Toplevel ();
top.Add (view);
Application.Begin (top);
((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
var expected = @"
┌──────────────────┐
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
│012345678910111213│
└──────────────────┘
"
;
Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, _output);
Assert.Equal (new (0, 0, 20, 10), pos);
view.FillRect (view.Viewport);
expected = @"
┌──────────────────┐
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────┘
";
pos = DriverAssert.AssertDriverContentsWithFrameAre (expected, _output);
top.Dispose ();
}
[Theory]
[AutoInitShutdown (configLocation: ConfigLocations.Default)]
[InlineData (true)]
[InlineData (false)]
public void Clear_Does_Not_Spillover_Its_Parent (bool label)
{
var root = new View { Width = 20, Height = 10, ColorScheme = Colors.ColorSchemes ["Base"] };
string text = new ('c', 100);
View v = label
// Label has Width/Height == AutoSize, so Frame.Size will be (100, 1)
? new Label { Text = text }
// TextView has Width/Height == (Dim.Fill, 1), so Frame.Size will be 20 (width of root), 1
: new TextView { Width = Dim.Fill (), Height = 1, Text = text };
root.Add (v);
var top = new Toplevel ();
top.Add (root);
RunState runState = Application.Begin (top);
Application.RunIteration (ref runState);
if (label)
{
Assert.False (v.CanFocus);
Assert.Equal (new (0, 0, text.Length, 1), v.Frame);
}
else
{
Assert.True (v.CanFocus);
Assert.Equal (new (0, 0, 20, 1), v.Frame);
}
DriverAssert.AssertDriverContentsWithFrameAre (
@"
cccccccccccccccccccc",
_output
);
Attribute [] attributes =
{
Colors.ColorSchemes ["TopLevel"].Normal,
Colors.ColorSchemes ["Base"].Normal,
Colors.ColorSchemes ["Base"].Focus
};
if (label)
{
DriverAssert.AssertDriverAttributesAre (
@"
111111111111111111110
111111111111111111110",
_output,
Application.Driver,
attributes
);
}
else
{
DriverAssert.AssertDriverAttributesAre (
@"
222222222222222222220
111111111111111111110",
_output,
Application.Driver,
attributes
);
}
if (label)
{
root.CanFocus = true;
v.CanFocus = true;
Assert.True (v.HasFocus);
v.SetFocus ();
Assert.True (v.HasFocus);
Application.LayoutAndDraw ();
DriverAssert.AssertDriverAttributesAre (
@"
222222222222222222220
111111111111111111110",
_output,
Application.Driver,
attributes
);
}
Application.End (runState);
top.Dispose ();
}
}

View File

@@ -0,0 +1,304 @@
#nullable enable
using System.Text;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
[Trait ("Category", "Output")]
public class ClipTests (ITestOutputHelper _output)
{
[Fact]
[SetupFakeDriver]
public void Move_Is_Not_Constrained_To_Viewport ()
{
var view = new View
{
X = 1,
Y = 1,
Width = 3, Height = 3
};
view.Margin!.Thickness = new (1);
view.Move (0, 0);
Assert.Equal (new (2, 2), new Point (Application.Driver!.Col, Application.Driver!.Row));
view.Move (-1, -1);
Assert.Equal (new (1, 1), new Point (Application.Driver!.Col, Application.Driver!.Row));
view.Move (1, 1);
Assert.Equal (new (3, 3), new Point (Application.Driver!.Col, Application.Driver!.Row));
}
[Fact]
[SetupFakeDriver]
public void AddRune_Is_Constrained_To_Viewport ()
{
var view = new View
{
X = 1,
Y = 1,
Width = 3, Height = 3
};
view.Padding!.Thickness = new (1);
view.Padding.Diagnostics = ViewDiagnosticFlags.Thickness;
view.BeginInit ();
view.EndInit ();
view.Draw ();
// Only valid location w/in Viewport is 0, 0 (view) - 2, 2 (screen)
Assert.Equal ((Rune)' ', Application.Driver?.Contents! [2, 2].Rune);
// When we exit Draw, the view is excluded from the clip. So drawing at 0,0, is not valid and is clipped.
view.AddRune (0, 0, Rune.ReplacementChar);
Assert.Equal ((Rune)' ', Application.Driver?.Contents! [2, 2].Rune);
view.AddRune (-1, -1, Rune.ReplacementChar);
Assert.Equal ((Rune)'P', Application.Driver?.Contents! [1, 1].Rune);
view.AddRune (1, 1, Rune.ReplacementChar);
Assert.Equal ((Rune)'P', Application.Driver?.Contents! [3, 3].Rune);
}
[Theory]
[InlineData (0, 0, 1, 1)]
[InlineData (0, 0, 2, 2)]
[InlineData (-1, -1, 2, 2)]
[SetupFakeDriver]
public void FillRect_Fills_HonorsClip (int x, int y, int width, int height)
{
var superView = new View { Width = Dim.Fill (), Height = Dim.Fill () };
var view = new View
{
Text = "X",
X = 1, Y = 1,
Width = 3, Height = 3,
BorderStyle = LineStyle.Single
};
superView.Add (view);
superView.BeginInit ();
superView.EndInit ();
superView.LayoutSubviews ();
superView.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
Rectangle toFill = new (x, y, width, height);
View.SetClipToScreen ();
view.FillRect (toFill);
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
// Now try to clear beyond Viewport (invalid; clipping should prevent)
superView.SetNeedsDraw ();
superView.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
toFill = new (-width, -height, width, height);
view.FillRect (toFill);
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
// Now try to clear beyond Viewport (valid)
superView.SetNeedsDraw ();
superView.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
toFill = new (-1, -1, width + 1, height + 1);
View.SetClipToScreen ();
view.FillRect (toFill);
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
// Now clear too much size
superView.SetNeedsDraw ();
superView.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│X│
└─┘",
_output);
toFill = new (0, 0, width * 2, height * 2);
View.SetClipToScreen ();
view.FillRect (toFill);
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌─┐
│ │
└─┘",
_output);
}
// TODO: Simplify this test to just use AddRune directly
[Fact]
[SetupFakeDriver]
[Trait ("Category", "Unicode")]
public void Clipping_Wide_Runes ()
{
((FakeDriver)Application.Driver!).SetBufferSize (30, 1);
var top = new View
{
Id = "top",
Width = Dim.Fill (),
Height = Dim.Fill ()
};
var frameView = new View
{
Id = "frameView",
Width = Dim.Fill (),
Height = Dim.Fill (),
Text = """
"""
};
frameView.Border!.LineStyle = LineStyle.Single;
frameView.Border.Thickness = new (1, 0, 0, 0);
top.Add (frameView);
View.SetClipToScreen ();
top.Layout ();
top.Draw ();
var expectedOutput = """
""";
DriverAssert.AssertDriverContentsWithFrameAre (expectedOutput, _output);
var view = new View
{
Text = "0123456789",
//Text = "ワイドルー。",
X = 2,
Height = Dim.Auto (),
Width = Dim.Auto (),
BorderStyle = LineStyle.Single
};
view.Border!.Thickness = new (1, 0, 1, 0);
top.Add (view);
top.Layout ();
View.SetClipToScreen ();
top.Draw ();
// 012345678901234567890123456789012345678
// 012 34 56 78 90 12 34 56 78 90 12 34 56 78
// │こ れ は 広 い ル ー ン ラ イ ン で す 。
// 01 2345678901234 56 78 90 12 34 56
// │<> |0123456989│<39> ン ラ イ ン で す 。
expectedOutput = """
<EFBFBD>0123456789<EFBFBD>
""";
DriverAssert.AssertDriverContentsWithFrameAre (expectedOutput, _output);
}
// TODO: Add more AddRune tests to cover all the cases where wide runes are clipped
[Fact]
[SetupFakeDriver]
public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped ()
{
// Screen is 25x25
// View is 25x25
// Viewport is (0, 0, 23, 23)
// ContentSize is (10, 10)
// ViewportToScreen is (1, 1, 23, 23)
// Visible content is (1, 1, 10, 10)
// Expected clip is (1, 1, 10, 10) - same as visible content
Rectangle expectedClip = new (1, 1, 10, 10);
// Arrange
var view = new View
{
Width = Dim.Fill (),
Height = Dim.Fill (),
ViewportSettings = ViewportSettings.ClipContentOnly
};
view.SetContentSize (new Size (10, 10));
view.Border!.Thickness = new (1);
view.BeginInit ();
view.EndInit ();
Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ());
// Act
view.AddViewportToClip ();
// Assert
Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ());
view.Dispose ();
}
[Fact]
[SetupFakeDriver]
public void SetClip_Default_ClipsToViewport ()
{
// Screen is 25x25
// View is 25x25
// Viewport is (0, 0, 23, 23)
// ContentSize is (10, 10)
// ViewportToScreen is (1, 1, 23, 23)
// Visible content is (1, 1, 10, 10)
// Expected clip is (1, 1, 23, 23) - same as Viewport
Rectangle expectedClip = new (1, 1, 23, 23);
// Arrange
var view = new View
{
Width = Dim.Fill (),
Height = Dim.Fill ()
};
view.SetContentSize (new Size (10, 10));
view.Border!.Thickness = new (1);
view.BeginInit ();
view.EndInit ();
Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ());
view.Viewport = view.Viewport with { X = 1, Y = 1 };
// Act
view.AddViewportToClip ();
// Assert
Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ());
view.Dispose ();
}
}

View File

@@ -0,0 +1,30 @@
#nullable enable
using UnitTests;
namespace Terminal.Gui.ViewTests;
[Trait ("Category", "Output")]
public class DrawEventTests
{
[Fact]
[AutoInitShutdown]
public void DrawContentComplete_Event_Is_Always_Called ()
{
var viewCalled = false;
var tvCalled = false;
var view = new View { Width = 10, Height = 10, Text = "View" };
view.DrawComplete += (s, e) => viewCalled = true;
var tv = new TextView { Y = 11, Width = 10, Height = 10 };
tv.DrawComplete += (s, e) => tvCalled = true;
var top = new Toplevel ();
top.Add (view, tv);
RunState runState = Application.Begin (top);
Application.RunIteration (ref runState);
Assert.True (viewCalled);
Assert.True (tvCalled);
top.Dispose ();
}
}

View File

@@ -0,0 +1,932 @@
#nullable enable
using System.Text;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
[Trait ("Category", "Output")]
public class DrawTests (ITestOutputHelper _output)
{
[Fact]
[AutoInitShutdown]
[Trait ("Category", "Unicode")]
public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two ()
{
const string us = "\U0000f900";
var r = (Rune)0xf900;
Assert.Equal ("豈", us);
Assert.Equal ("豈", r.ToString ());
Assert.Equal (us, r.ToString ());
Assert.Equal (2, us.GetColumns ());
Assert.Equal (2, r.GetColumns ());
var win = new Window { Title = us };
var view = new View { Text = r.ToString (), Height = Dim.Fill (), Width = Dim.Fill () };
var tf = new TextField { Text = us, Y = 1, Width = 3 };
win.Add (view, tf);
Toplevel top = new ();
top.Add (win);
Application.Begin (top);
((FakeDriver)Application.Driver!).SetBufferSize (10, 4);
const string expectedOutput = """
""";
DriverAssert.AssertDriverContentsWithFrameAre (expectedOutput, _output);
DriverAssert.AssertDriverContentsAre (expectedOutput, _output);
// This test has nothing to do with color - removing as it is not relevant and fragile
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
[Trait ("Category", "Output")]
public void Colors_On_TextAlignment_Right_And_Bottom ()
{
var viewRight = new View
{
Text = "Test",
Width = 6,
Height = 1,
TextAlignment = Alignment.End,
ColorScheme = Colors.ColorSchemes ["Base"]
};
var viewBottom = new View
{
Text = "Test",
TextDirection = TextDirection.TopBottom_LeftRight,
Y = 1,
Width = 1,
Height = 6,
VerticalTextAlignment = Alignment.End,
ColorScheme = Colors.ColorSchemes ["Base"]
};
Toplevel top = new ();
top.Add (viewRight, viewBottom);
var rs = Application.Begin (top);
((FakeDriver)Application.Driver!).SetBufferSize (7, 7);
Application.RunIteration (ref rs);
DriverAssert.AssertDriverContentsWithFrameAre (
"""
Test
T
e
s
t
""",
_output
);
DriverAssert.AssertDriverAttributesAre (
"""
000000
0
0
0
0
0
0
""",
_output,
Application.Driver,
Colors.ColorSchemes ["Base"]!.Normal
);
top.Dispose ();
}
[Fact]
[SetupFakeDriver]
public void Draw_Minimum_Full_Border_With_Empty_Viewport ()
{
var view = new View { Width = 2, Height = 2, BorderStyle = LineStyle.Single };
Assert.True (view.NeedsLayout);
Assert.True (view.NeedsDraw);
view.Layout ();
Assert.Equal (new (0, 0, 2, 2), view.Frame);
Assert.Equal (Rectangle.Empty, view.Viewport);
Assert.True (view.NeedsDraw);
view.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
""",
_output
);
}
[Fact]
[SetupFakeDriver]
public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Bottom ()
{
var view = new View { Width = 2, Height = 1, BorderStyle = LineStyle.Single };
view.Border!.Thickness = new (1, 1, 1, 0);
view.BeginInit ();
view.EndInit ();
view.SetRelativeLayout (Application.Screen.Size);
Assert.Equal (new (0, 0, 2, 1), view.Frame);
Assert.Equal (Rectangle.Empty, view.Viewport);
view.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre ("──", _output);
}
[Fact]
[SetupFakeDriver]
public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Left ()
{
var view = new View { Width = 1, Height = 2, BorderStyle = LineStyle.Single };
view.Border!.Thickness = new (0, 1, 1, 1);
view.BeginInit ();
view.EndInit ();
view.SetRelativeLayout (Application.Screen.Size);
Assert.Equal (new (0, 0, 1, 2), view.Frame);
Assert.Equal (Rectangle.Empty, view.Viewport);
view.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
""",
_output
);
}
[Fact]
[SetupFakeDriver]
public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Right ()
{
var view = new View { Width = 1, Height = 2, BorderStyle = LineStyle.Single };
view.Border!.Thickness = new (1, 1, 0, 1);
view.BeginInit ();
view.EndInit ();
view.SetRelativeLayout (Application.Screen.Size);
Assert.Equal (new (0, 0, 1, 2), view.Frame);
Assert.Equal (Rectangle.Empty, view.Viewport);
view.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
""",
_output
);
}
[Fact]
[SetupFakeDriver]
public void Draw_Minimum_Full_Border_With_Empty_Viewport_Without_Top ()
{
var view = new View { Width = 2, Height = 1, BorderStyle = LineStyle.Single };
view.Border!.Thickness = new (1, 0, 1, 1);
view.BeginInit ();
view.EndInit ();
view.SetRelativeLayout (Application.Screen.Size);
Assert.Equal (new (0, 0, 2, 1), view.Frame);
Assert.Equal (Rectangle.Empty, view.Viewport);
view.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"││",
_output
);
}
[Fact]
[AutoInitShutdown]
public void Draw_Negative_Viewport_Horizontal_With_New_Lines ()
{
var subView = new View
{
Id = "subView",
X = 1,
Width = 1,
Height = 7,
Text = """
s
u
b
V
i
e
w
"""
};
var view = new View
{
Id = "view", Width = 2, Height = 20, Text = """
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
"""
};
view.Add (subView);
var content = new View { Id = "content", Width = 20, Height = 20 };
content.Add (view);
var container = new View
{
Id = "container",
X = 1,
Y = 1,
Width = 5,
Height = 5
};
container.Add (content);
Toplevel top = new ();
top.Add (container);
var rs = Application.Begin (top);
top.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
0s
1u
2b
3V
4i
""",
_output
);
content.X = -1;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
s
u
b
V
i
""",
_output
);
content.X = -2;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (@"", _output);
content.X = 0;
content.Y = -1;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
1u
2b
3V
4i
5e
""",
_output
);
content.Y = -6;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
6w
7
8
9
0
""",
_output
);
content.Y = -19;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
9
""",
_output
);
content.Y = -20;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre ("", _output);
content.X = -2;
content.Y = 0;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre ("", _output);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Draw_Negative_Viewport_Horizontal_Without_New_Lines ()
{
// BUGBUG: This previously assumed the default height of a View was 1.
var subView = new View
{
Id = "subView",
Y = 1,
Width = 7,
Height = 1,
Text = "subView"
};
var view = new View { Id = "view", Width = 20, Height = 2, Text = "01234567890123456789" };
view.Add (subView);
var content = new View { Id = "content", Width = 20, Height = 20 };
content.Add (view);
var container = new View
{
Id = "container",
X = 1,
Y = 1,
Width = 5,
Height = 5
};
container.Add (content);
Toplevel top = new ();
top.Add (container);
// BUGBUG: v2 - it's bogus to reference .Frame before BeginInit. And why is the clip being set anyway???
top.SubviewsLaidOut += Top_LayoutComplete;
Application.Begin (top);
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
01234
subVi
""",
_output
);
content.X = -1;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
12345
ubVie
""",
_output
);
content.Y = -1;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
ubVie
""",
_output
);
content.Y = -2;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre ("", _output);
content.X = -20;
content.Y = 0;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre ("", _output);
top.Dispose ();
return;
void Top_LayoutComplete (object? sender, LayoutEventArgs e) { Application.Driver!.Clip = new (container.Frame); }
}
[Fact]
[AutoInitShutdown]
public void Draw_Negative_Viewport_Vertical ()
{
var subView = new View
{
Id = "subView",
X = 1,
Width = 1,
Height = 7,
Text = "subView",
TextDirection = TextDirection.TopBottom_LeftRight
};
var view = new View
{
Id = "view",
Width = 2,
Height = 20,
Text = "01234567890123456789",
TextDirection = TextDirection.TopBottom_LeftRight
};
view.Add (subView);
var content = new View { Id = "content", Width = 20, Height = 20 };
content.Add (view);
var container = new View
{
Id = "container",
X = 1,
Y = 1,
Width = 5,
Height = 5
};
container.Add (content);
Toplevel top = new ();
top.Add (container);
Application.Begin (top);
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
0s
1u
2b
3V
4i
""",
_output
);
content.X = -1;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
s
u
b
V
i
""",
_output
);
content.X = -2;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (@"", _output);
content.X = 0;
content.Y = -1;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
1u
2b
3V
4i
5e
""",
_output
);
content.Y = -6;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
6w
7
8
9
0
""",
_output
);
content.Y = -19;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre (
"""
9
""",
_output
);
content.Y = -20;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre ("", _output);
content.X = -2;
content.Y = 0;
Application.LayoutAndDraw ();
DriverAssert.AssertDriverContentsWithFrameAre ("", _output);
top.Dispose ();
}
[Theory]
[SetupFakeDriver]
[InlineData ("𝔽𝕆𝕆𝔹𝔸R")]
[InlineData ("a𐐀b")]
public void DrawHotString_NonBmp (string expected)
{
var view = new View { Width = 10, Height = 1 };
view.DrawHotString (expected, Attribute.Default, Attribute.Default);
DriverAssert.AssertDriverContentsWithFrameAre (expected, _output);
}
// TODO: The tests below that use Label should use View instead.
[Fact]
[AutoInitShutdown]
public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two ()
{
var us = "\U0001d539";
var r = (Rune)0x1d539;
Assert.Equal ("𝔹", us);
Assert.Equal ("𝔹", r.ToString ());
Assert.Equal (us, r.ToString ());
Assert.Equal (1, us.GetColumns ());
Assert.Equal (1, r.GetColumns ());
var win = new Window { Title = us };
var view = new Label { Text = r.ToString () };
var tf = new TextField { Text = us, Y = 1, Width = 3 };
win.Add (view, tf);
Toplevel top = new ();
top.Add (win);
Application.Begin (top);
((FakeDriver)Application.Driver!).SetBufferSize (10, 4);
var expected = """
𝔹
𝔹
𝔹
""";
DriverAssert.AssertDriverContentsWithFrameAre (expected, _output);
DriverAssert.AssertDriverContentsAre (expected, _output);
top.Dispose ();
// This test has nothing to do with color - removing as it is not relevant and fragile
}
[Fact]
[TestRespondersDisposed]
public void Draw_Throws_IndexOutOfRangeException_With_Negative_Bounds ()
{
Application.Init (new FakeDriver ());
Toplevel top = new ();
var view = new View { X = -2, Text = "view" };
top.Add (view);
Application.Iteration += (s, a) =>
{
Assert.Equal (-2, view.X);
Application.RequestStop ();
};
try
{
Application.Run (top);
}
catch (IndexOutOfRangeException ex)
{
// After the fix this exception will not be caught.
Assert.IsType<IndexOutOfRangeException> (ex);
}
top.Dispose ();
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
[Fact]
[AutoInitShutdown]
public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Frame ()
{
var label = new Label { Text = "At 0,0" };
var view = new DerivedView
{
X = 2,
Y = 2,
Width = 30,
Height = 2,
Text = "A text with some long width\n and also with two lines."
};
Toplevel top = new ();
top.Add (label, view);
RunState runState = Application.Begin (top);
Application.RunIteration (ref runState);
DriverAssert.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text with some long width
and also with two lines. "
,
_output
);
view.Frame = new (3, 3, 10, 1);
Assert.Equal (new (3, 3, 10, 1), view.Frame);
Assert.Equal (new (0, 0, 10, 1), view.Viewport);
Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
//Application.Refresh();
top.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text wit",
_output
);
Application.End (runState);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Down_Right_Using_Pos_Dim ()
{
var label = new Label { Text = "At 0,0" };
var view = new DerivedView
{
X = 2,
Y = 2,
Width = 30,
Height = 2,
Text = "A text with some long width\n and also with two lines."
};
Toplevel top = new ();
top.Add (label, view);
RunState runState = Application.Begin (top);
top.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text with some long width
and also with two lines. "
,
_output
);
view.X = 3;
view.Y = 3;
view.Width = 10;
view.Height = 1;
Assert.Equal (new (3, 3, 10, 1), view.Frame);
Assert.Equal (new (0, 0, 10, 1), view.Viewport);
Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
View.SetClipToScreen ();
top.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text wit"
,
_output
);
Application.End (runState);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Frame ()
{
var label = new Label { Text = "At 0,0" };
var view = new DerivedView
{
X = 2,
Y = 2,
Width = 30,
Height = 2,
Text = "A text with some long width\n and also with two lines."
};
Toplevel top = new ();
top.Add (label, view);
RunState runState = Application.Begin (top);
Application.RunIteration (ref runState);
DriverAssert.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text with some long width
and also with two lines. "
,
_output
);
view.Frame = new (1, 1, 10, 1);
Assert.Equal (new (1, 1, 10, 1), view.Frame);
Assert.Equal (new (0, 0, 10, 1), view.Viewport);
Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
top.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text wit"
,
_output
);
Application.End (runState);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Correct_Redraw_Viewport_NeedDisplay_On_Shrink_And_Move_Up_Left_Using_Pos_Dim ()
{
var label = new Label { Text = "At 0,0" };
var view = new DerivedView
{
X = 2,
Y = 2,
Width = 30,
Height = 2,
Text = "A text with some long width\n and also with two lines."
};
Toplevel top = new ();
top.Add (label, view);
RunState runState = Application.Begin (top);
top.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text with some long width
and also with two lines. "
,
_output
);
view.X = 1;
view.Y = 1;
view.Width = 10;
view.Height = 1;
Assert.Equal (new (1, 1, 10, 1), view.Frame);
Assert.Equal (new (0, 0, 10, 1), view.Viewport);
Assert.Equal (new (0, 0, 10, 1), view._needsDrawRect);
View.SetClipToScreen ();
top.Draw ();
DriverAssert.AssertDriverContentsWithFrameAre (
@"
At 0,0
A text wit"
,
_output
);
Application.End (runState);
top.Dispose ();
}
public class DerivedView : View
{
public DerivedView () { CanFocus = true; }
public bool IsKeyDown { get; set; }
public bool IsKeyPress { get; set; }
public bool IsKeyUp { get; set; }
public override string Text { get; set; }
protected override bool OnDrawingContent ()
{
var idx = 0;
// BUGBUG: v2 - this should use Viewport, not Frame
for (var r = 0; r < Frame.Height; r++)
{
for (var c = 0; c < Frame.Width; c++)
{
if (idx < Text.Length)
{
char rune = Text [idx];
if (rune != '\n')
{
AddRune (c, r, (Rune)Text [idx]);
}
idx++;
if (rune == '\n')
{
break;
}
}
}
}
ClearNeedsDraw ();
return true;
}
protected override bool OnKeyDown (Key keyEvent)
{
IsKeyDown = true;
return true;
}
public override bool OnKeyUp (Key keyEvent)
{
IsKeyUp = true;
return true;
}
protected override bool OnKeyDownNotHandled (Key keyEvent)
{
IsKeyPress = true;
return true;
}
}
}

View File

@@ -0,0 +1,67 @@
#nullable enable
using UnitTests;
namespace Terminal.Gui.ViewTests;
[Trait ("Category", "Output")]
public class NeedsDrawTests ()
{
[Fact]
[AutoInitShutdown]
public void Frame_Set_After_Initialize_Update_NeededDisplay ()
{
var frame = new FrameView ();
var label = new Label
{
ColorScheme = Colors.ColorSchemes ["Menu"], X = 0, Y = 0, Text = "This should be the first line."
};
var view = new View
{
X = 0, // don't overcomplicate unit tests
Y = 1,
Height = Dim.Auto (DimAutoStyle.Text),
Width = Dim.Auto (DimAutoStyle.Text),
Text = "Press me!"
};
frame.Add (label, view);
frame.X = Pos.Center ();
frame.Y = Pos.Center ();
frame.Width = 40;
frame.Height = 8;
Toplevel top = new ();
top.Add (frame);
RunState runState = Application.Begin (top);
top.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 80, 25), top._needsDrawRect); };
frame.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 40, 8), frame._needsDrawRect); };
label.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 38, 1), label._needsDrawRect); };
view.SubviewsLaidOut += (s, e) => { Assert.Equal (new (0, 0, 13, 1), view._needsDrawRect); };
Assert.Equal (new (0, 0, 80, 25), top.Frame);
Assert.Equal (new (20, 8, 40, 8), frame.Frame);
Assert.Equal (
new (20, 8, 60, 16),
new Rectangle (
frame.Frame.Left,
frame.Frame.Top,
frame.Frame.Right,
frame.Frame.Bottom
)
);
Assert.Equal (new (0, 0, 30, 1), label.Frame);
Assert.Equal (new (0, 1, 9, 1), view.Frame); // this proves frame was set
Application.End (runState);
top.Dispose ();
}
}

View File

@@ -0,0 +1,109 @@
#nullable enable
using System.Text;
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
[Trait ("Category", "Output")]
public class TransparentTests (ITestOutputHelper _output)
{
[Fact]
[SetupFakeDriver]
public void Transparent_Text_Occludes ()
{
var super = new View
{
Id = "super",
Width = 20,
Height = 5,
};
super.DrawingContent += (sender, args) =>
{
var s = sender as View;
s!.FillRect(s!.Viewport, Glyphs.Stipple);
args.Cancel = true;
};
var sub = new View
{
X = 1,
Y = 1,
Width = 15,
Height = 3,
Id = "sub",
Text = "Sub",
ViewportSettings = ViewportSettings.Transparent,
BorderStyle = LineStyle.Single
};
super.Add (sub);
super.Layout ();
super.Draw ();
_ = DriverAssert.AssertDriverContentsWithFrameAre (
@"
░░░░░░░░░░░░░░░░░░░░
░┌─────────────┐░░░░
░│Sub░░░░░░░░░░│░░░░
░└─────────────┘░░░░
░░░░░░░░░░░░░░░░░░░░", _output);
}
[Fact]
[SetupFakeDriver]
public void Transparent_Subview_Occludes ()
{
var super = new View
{
Id = "super",
Width = 20,
Height = 5,
};
super.DrawingContent += (sender, args) =>
{
var s = sender as View;
s!.FillRect (s!.Viewport, Glyphs.Stipple);
args.Cancel = true;
};
var sub = new View
{
X = 1,
Y = 1,
Width = 15,
Height = 3,
Id = "sub",
ViewportSettings = ViewportSettings.Transparent,
BorderStyle = LineStyle.Single
};
var subSub = new View
{
X = Pos.Center(),
Y = Pos.Center(),
Width = Dim.Auto(),
Height = Dim.Auto(),
Id = "subSub",
Text = "subSub",
};
sub.Add (subSub);
super.Add (sub);
super.Layout ();
super.Draw ();
_ = DriverAssert.AssertDriverContentsWithFrameAre (
@"
░░░░░░░░░░░░░░░░░░░░
░┌─────────────┐░░░░
░│░░░subSub░░░░│░░░░
░└─────────────┘░░░░
░░░░░░░░░░░░░░░░░░░░", _output);
}
}

View File

@@ -0,0 +1,165 @@
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
/// <summary>Tests View BeginInit/EndInit/Initialized functionality.</summary>
public class InitTests
{
private readonly ITestOutputHelper _output;
public InitTests (ITestOutputHelper output) { _output = output; }
// Test behavior of calling BeginInit multiple times
[Fact]
public void BeginInit_Called_Multiple_Times_Throws ()
{
var view = new View ();
var superView = new View ();
superView.Add (view);
Assert.False (view.IsInitialized, "View should not be initialized");
Assert.False (superView.IsInitialized, "SuperView should not be initialized");
superView.BeginInit ();
superView.EndInit ();
Assert.True (view.IsInitialized, "View should be initialized");
Assert.True (superView.IsInitialized, "SuperView should be initialized");
Assert.Throws<InvalidOperationException> (() => superView.BeginInit ());
}
[Fact]
public void BeginInit_EndInit_Initialized ()
{
var view = new View ();
Assert.False (view.IsInitialized, "View should not be initialized");
view.BeginInit ();
Assert.False (view.IsInitialized, "View should not be initialized");
view.EndInit ();
Assert.True (view.IsInitialized, "View should be initialized");
}
[Fact]
public void BeginInit_EndInit_Initialized_WithSuperView ()
{
var view = new View ();
var superView = new View ();
superView.Add (view);
Assert.False (view.IsInitialized, "View should not be initialized");
view.BeginInit ();
Assert.False (view.IsInitialized, "View should not be initialized");
view.EndInit ();
Assert.True (view.IsInitialized, "View should be initialized");
Assert.False (superView.IsInitialized, "SuperView should not be initialized");
}
[Fact]
public void BeginInit_EndInit_SuperView_Initialized ()
{
var view = new View ();
var superView = new View ();
superView.Add (view);
Assert.False (view.IsInitialized, "View should not be initialized");
Assert.False (superView.IsInitialized, "SuperView should not be initialized");
superView.BeginInit ();
Assert.False (view.IsInitialized, "View should not be initialized");
Assert.False (superView.IsInitialized, "SuperView should not be initialized");
superView.EndInit ();
Assert.True (view.IsInitialized, "View should be initialized");
Assert.True (superView.IsInitialized, "SuperView should be initialized");
}
[Fact]
public void BeginInit_EndInit_SuperView_Initialized_WithSuperSuperView ()
{
var view = new View ();
var superView = new View ();
var superSuperView = new View ();
superSuperView.Add (superView);
superView.Add (view);
Assert.False (view.IsInitialized, "View should not be initialized");
Assert.False (superView.IsInitialized, "SuperView should not be initialized");
Assert.False (superSuperView.IsInitialized, "SuperSuperView should not be initialized");
superSuperView.BeginInit ();
Assert.False (view.IsInitialized, "View should not be initialized");
Assert.False (superView.IsInitialized, "SuperView should not be initialized");
Assert.False (superSuperView.IsInitialized, "SuperSuperView should not be initialized");
superSuperView.EndInit ();
Assert.True (view.IsInitialized, "View should be initialized");
Assert.True (superView.IsInitialized, "SuperView should be initialized");
Assert.True (superSuperView.IsInitialized, "SuperSuperView should be initialized");
}
// Test behavior of calling EndInit multiple times
[Fact]
public void EndInit_Called_Multiple_Times_Throws ()
{
var view = new View ();
var superView = new View ();
superView.Add (view);
var initialized = false;
view.Initialized += (s, e) => initialized = true;
var superViewInitialized = false;
superView.Initialized += (s, e) => superViewInitialized = true;
Assert.False (view.IsInitialized, "View should not be initialized");
Assert.False (superView.IsInitialized, "SuperView should not be initialized");
superView.BeginInit ();
superView.EndInit ();
Assert.True (view.IsInitialized, "View should be initialized");
Assert.True (superView.IsInitialized, "SuperView should be initialized");
Assert.True (initialized, "View: Initialized event should have been raised");
Assert.True (superViewInitialized, "SuperView: Initialized event should have been raised");
Assert.Throws<InvalidOperationException> (() => superView.EndInit ());
}
// Test calling EndInit without first calling BeginInit
[Fact]
public void EndInit_Called_Without_BeginInit_Throws ()
{
var view = new View ();
var superView = new View ();
superView.Add (view);
//var initialized = false;
//view.Initialized += (s, e) => initialized = true;
//var superViewInitialized = false;
//superView.Initialized += (s, e) => superViewInitialized = true;
Assert.False (view.IsInitialized, "View should not be initialized");
Assert.False (superView.IsInitialized, "SuperView should not be initialized");
// TODO: Implement logic that does this in Begin/EndInit
//Assert.Throws<InvalidOperationException> (() => superView.EndInit ());
}
// Initialized event
[Fact]
public void InitializedEvent_Fires_On_EndInit ()
{
var view = new View ();
var superView = new View ();
superView.Add (view);
var initialized = false;
view.Initialized += (s, e) => initialized = true;
var superViewInitialized = false;
superView.Initialized += (s, e) => superViewInitialized = true;
Assert.False (initialized, "View: Initialized event should not have been raised");
Assert.False (superViewInitialized, "SuperView: Initialized event should not have been raised");
superView.BeginInit ();
Assert.False (initialized, "View: Initialized event should not have been raised");
Assert.False (superViewInitialized, "SuperView: Initialized event should not have been raised");
superView.EndInit ();
Assert.True (initialized, "View: Initialized event should have been raised");
Assert.True (superViewInitialized, "SuperView: Initialized event should have been raised");
}
// TODO: Create tests that prove ISupportInitialize and ISupportInitializeNotifications work properly
}

View File

@@ -0,0 +1,421 @@
using System.Text;
using UICatalog.Scenarios;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class HotKeyTests
{
private readonly ITestOutputHelper _output;
public HotKeyTests (ITestOutputHelper output) { _output = output; }
[Theory]
[InlineData (KeyCode.A)]
[InlineData (KeyCode.A | KeyCode.ShiftMask)]
[InlineData (KeyCode.D1)]
[InlineData (KeyCode.D1 | KeyCode.ShiftMask)] // '!'
[InlineData ((KeyCode)'х')] // Cyrillic x
[InlineData ((KeyCode)'你')] // Chinese ni
public void AddKeyBindingsForHotKey_Sets (KeyCode key)
{
var view = new View ();
view.HotKey = KeyCode.Z;
Assert.Equal (string.Empty, view.Title);
Assert.Equal (KeyCode.Z, view.HotKey);
view.AddKeyBindingsForHotKey (KeyCode.Null, key);
// Verify key bindings were set
// As passed
Command [] commands = view.HotKeyBindings.GetCommands (key);
Assert.Contains (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (key | KeyCode.AltMask);
Assert.Contains (Command.HotKey, commands);
KeyCode baseKey = key & ~KeyCode.ShiftMask;
// If A...Z, with and without shift
if (baseKey is >= KeyCode.A and <= KeyCode.Z)
{
commands = view.HotKeyBindings.GetCommands (key | KeyCode.ShiftMask);
Assert.Contains (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (key & ~KeyCode.ShiftMask);
Assert.Contains (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (key | KeyCode.AltMask);
Assert.Contains (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands ((key & ~KeyCode.ShiftMask) | KeyCode.AltMask);
Assert.Contains (Command.HotKey, commands);
}
else
{
// Non A..Z keys should not have shift bindings
if (key.HasFlag (KeyCode.ShiftMask))
{
commands = view.HotKeyBindings.GetCommands (key & ~KeyCode.ShiftMask);
Assert.Empty (commands);
}
else
{
commands = view.HotKeyBindings.GetCommands (key | KeyCode.ShiftMask);
Assert.Empty (commands);
}
}
}
[Fact]
public void AddKeyBindingsForHotKey_SetsBinding_Key ()
{
var view = new View ();
view.HotKey = KeyCode.Z;
Assert.Equal (string.Empty, view.Title);
Assert.Equal (KeyCode.Z, view.HotKey);
view.AddKeyBindingsForHotKey (view.HotKey, Key.A);
view.HotKeyBindings.TryGet (Key.A, out var binding);
Assert.Equal (Key.A, binding.Key);
}
[Fact]
public void AddKeyBindingsForHotKey_SetsBinding_Data ()
{
var view = new View ();
view.HotKey = KeyCode.Z;
Assert.Equal (KeyCode.Z, view.HotKey);
view.AddKeyBindingsForHotKey (view.HotKey, Key.A, "data");
view.HotKeyBindings.TryGet (Key.A, out var binding);
Assert.Equal ("data", binding.Data);
}
[Fact]
public void Defaults ()
{
var view = new View ();
Assert.Equal (string.Empty, view.Title);
Assert.Equal (KeyCode.Null, view.HotKey);
// Verify key bindings were set
Command [] commands = view.KeyBindings.GetCommands (KeyCode.Null);
Assert.Empty (commands);
commands = view.HotKeyBindings.GetCommands (KeyCode.Null);
Assert.Empty (commands);
Assert.Empty (view.HotKeyBindings.GetBindings ());
}
[Theory]
[InlineData (KeyCode.Null, true)] // non-shift
[InlineData (KeyCode.ShiftMask, true)]
[InlineData (KeyCode.AltMask, true)]
[InlineData (KeyCode.ShiftMask | KeyCode.AltMask, true)]
[InlineData (KeyCode.CtrlMask, false)]
[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask, false)]
public void NewKeyDownEvent_Runs_Default_HotKey_Command (KeyCode mask, bool expected)
{
var view = new View { HotKeySpecifier = (Rune)'^', Title = "^Test" };
view.CanFocus = true;
Assert.False (view.HasFocus);
view.NewKeyDownEvent (KeyCode.T | mask);
Assert.Equal (expected, view.HasFocus);
}
[Fact]
public void NewKeyDownEvent_Ignores_Focus_KeyBindings_SuperView ()
{
var view = new View ();
view.HotKeyBindings.Add (Key.A, Command.HotKey);
view.KeyDownNotHandled += (s, e) => { Assert.Fail (); };
var superView = new View ();
superView.Add (view);
var ke = Key.A;
superView.NewKeyDownEvent (ke);
}
[Fact]
public void NewKeyDownEvent_Honors_HotKey_KeyBindings_SuperView ()
{
var view = new View ();
view.HotKeyBindings.Add (Key.A, Command.HotKey);
bool hotKeyInvoked = false;
view.HandlingHotKey += (s, e) => { hotKeyInvoked = true; };
bool notHandled = false;
view.KeyDownNotHandled += (s, e) => { notHandled = true; };
var superView = new View ();
superView.Add (view);
var ke = Key.A;
superView.NewKeyDownEvent (ke);
Assert.False (notHandled);
Assert.True (hotKeyInvoked);
}
[Fact]
public void NewKeyDownEvent_InNewKeyDownEvent_Invokes_HotKey_Command_With_SuperView ()
{
var superView = new View ()
{
CanFocus = true
};
var view1 = new View
{
HotKeySpecifier = (Rune)'^',
Title = "view^1",
CanFocus = true
};
var view2 = new View
{
HotKeySpecifier = (Rune)'^',
Title = "view^2",
CanFocus = true
};
superView.Add (view1, view2);
superView.SetFocus ();
Assert.True (view1.HasFocus);
var ke = Key.D2;
superView.NewKeyDownEvent (ke);
Assert.True (view2.HasFocus);
}
[Fact]
public void Set_RemovesOldKeyBindings ()
{
var view = new View ();
view.HotKey = KeyCode.A;
Assert.Equal (string.Empty, view.Title);
Assert.Equal (KeyCode.A, view.HotKey);
// Verify key bindings were set
Command [] commands = view.HotKeyBindings.GetCommands (KeyCode.A);
Assert.Contains (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.ShiftMask);
Assert.Contains (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.AltMask);
Assert.Contains (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask);
Assert.Contains (Command.HotKey, commands);
// Now set again
view.HotKey = KeyCode.B;
Assert.Equal (string.Empty, view.Title);
Assert.Equal (KeyCode.B, view.HotKey);
commands = view.HotKeyBindings.GetCommands (KeyCode.A);
Assert.DoesNotContain (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.ShiftMask);
Assert.DoesNotContain (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.AltMask);
Assert.DoesNotContain (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask);
Assert.DoesNotContain (Command.HotKey, commands);
}
[Theory]
[InlineData (KeyCode.A)]
[InlineData (KeyCode.A | KeyCode.ShiftMask)]
[InlineData (KeyCode.D1)]
[InlineData (KeyCode.D1 | KeyCode.ShiftMask)]
[InlineData ((KeyCode)'!')]
[InlineData ((KeyCode)'х')] // Cyrillic x
[InlineData ((KeyCode)'你')] // Chinese ni
[InlineData ((KeyCode)'ö')] // German o umlaut
[InlineData (KeyCode.Null)]
public void Set_Sets_WithValidKey (KeyCode key)
{
var view = new View ();
view.HotKey = key;
Assert.Equal (key, view.HotKey);
}
[Theory]
[InlineData (KeyCode.A)]
[InlineData (KeyCode.A | KeyCode.ShiftMask)]
[InlineData (KeyCode.D1)]
[InlineData (KeyCode.D1 | KeyCode.ShiftMask)] // '!'
[InlineData ((KeyCode)'х')] // Cyrillic x
[InlineData ((KeyCode)'你')] // Chinese ni
[InlineData ((KeyCode)'ö')] // German o umlaut
public void Set_SetsKeyBindings (KeyCode key)
{
var view = new View ();
view.HotKey = key;
Assert.Equal (string.Empty, view.Title);
Assert.Equal (key, view.HotKey);
// Verify key bindings were set
// As passed
Command [] commands = view.HotKeyBindings.GetCommands (view.HotKey);
Assert.Contains (Command.HotKey, commands);
Key baseKey = view.HotKey.NoShift;
// If A...Z, with and without shift
if (baseKey.IsKeyCodeAtoZ)
{
commands = view.HotKeyBindings.GetCommands (view.HotKey.WithShift);
Assert.Contains (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (view.HotKey.NoShift);
Assert.Contains (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (view.HotKey.WithAlt);
Assert.Contains (Command.HotKey, commands);
commands = view.HotKeyBindings.GetCommands (view.HotKey.NoShift.WithAlt);
Assert.Contains (Command.HotKey, commands);
}
else
{
// Non A..Z keys should not have shift bindings
if (view.HotKey.IsShift)
{
commands = view.HotKeyBindings.GetCommands (view.HotKey.NoShift);
Assert.Empty (commands);
}
else
{
commands = view.HotKeyBindings.GetCommands (view.HotKey.WithShift);
Assert.Empty (commands);
}
}
}
[Fact]
public void Set_Throws_If_Modifiers_Are_Included ()
{
var view = new View ();
// A..Z must be naked (Alt is assumed)
view.HotKey = Key.A.WithAlt;
Assert.Throws<ArgumentException> (() => view.HotKey = Key.A.WithCtrl);
Assert.Throws<ArgumentException> (
() =>
view.HotKey =
KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask
);
// All others must not have Ctrl (Alt is assumed)
view.HotKey = Key.D1.WithAlt;
Assert.Throws<ArgumentException> (() => view.HotKey = Key.D1.WithCtrl);
Assert.Throws<ArgumentException> (
() =>
view.HotKey =
KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask
);
// Shift is ok (e.g. this is '!')
view.HotKey = Key.D1.WithShift;
}
[Theory]
[InlineData (KeyCode.Delete)]
[InlineData (KeyCode.Backspace)]
[InlineData (KeyCode.Tab)]
[InlineData (KeyCode.Enter)]
[InlineData (KeyCode.Esc)]
[InlineData (KeyCode.Space)]
[InlineData (KeyCode.CursorLeft)]
[InlineData (KeyCode.F1)]
[InlineData (KeyCode.Null | KeyCode.ShiftMask)]
public void Set_Throws_With_Invalid_Key (KeyCode key)
{
var view = new View ();
Assert.Throws<ArgumentException> (() => view.HotKey = key);
}
[Theory]
[InlineData ("Test", KeyCode.Null)]
[InlineData ("^Test", KeyCode.T)]
[InlineData ("T^est", KeyCode.E)]
[InlineData ("Te^st", KeyCode.S)]
[InlineData ("Tes^t", KeyCode.T)]
[InlineData ("other", KeyCode.Null)]
[InlineData ("oTher", KeyCode.Null)]
[InlineData ("^Öther", (KeyCode)'Ö')]
[InlineData ("^öther", (KeyCode)'ö')]
// BUGBUG: '!' should be supported. Line 968 of TextFormatter filters on char.IsLetterOrDigit
//[InlineData ("Test^!", (Key)'!')]
public void Title_Change_Sets_HotKey (string title, KeyCode expectedHotKey)
{
var view = new View { HotKeySpecifier = new Rune ('^'), Title = "^Hello" };
Assert.Equal (KeyCode.H, view.HotKey);
view.Title = title;
Assert.Equal (expectedHotKey, view.HotKey);
}
[Theory]
[InlineData ("^Test")]
public void Title_Empty_Sets_HotKey_To_Null (string title)
{
var view = new View { HotKeySpecifier = (Rune)'^', Title = title };
Assert.Equal (title, view.Title);
Assert.Equal (KeyCode.T, view.HotKey);
view.Title = string.Empty;
Assert.Equal ("", view.Title);
Assert.Equal (KeyCode.Null, view.HotKey);
}
[Fact]
public void HotKey_Raises_HotKeyCommand ()
{
var hotKeyRaised = false;
var acceptRaised = false;
var selectRaised = false;
Application.Top = new Toplevel ();
var view = new View
{
CanFocus = true,
HotKeySpecifier = new Rune ('_'),
Title = "_Test"
};
Application.Top.Add (view);
view.HandlingHotKey += (s, e) => hotKeyRaised = true;
view.Accepting += (s, e) => acceptRaised = true;
view.Selecting += (s, e) => selectRaised = true;
Assert.Equal (KeyCode.T, view.HotKey);
Assert.True (Application.RaiseKeyDownEvent (Key.T));
Assert.True (hotKeyRaised);
Assert.False (acceptRaised);
Assert.False (selectRaised);
hotKeyRaised = false;
Assert.True (Application.RaiseKeyDownEvent (Key.T.WithAlt));
Assert.True (hotKeyRaised);
Assert.False (acceptRaised);
Assert.False (selectRaised);
hotKeyRaised = false;
view.HotKey = KeyCode.E;
Assert.True (Application.RaiseKeyDownEvent (Key.E.WithAlt));
Assert.True (hotKeyRaised);
Assert.False (acceptRaised);
Assert.False (selectRaised);
Application.Top.Dispose ();
Application.ResetState (true);
}
}

View File

@@ -0,0 +1,151 @@
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
/// <summary>
/// Tests for View.KeyBindings
/// </summary>
public class KeyBindingsTests ()
{
[Fact]
[AutoInitShutdown]
public void Focused_HotKey_Application_All_Work ()
{
var view = new ScopedKeyBindingView ();
var keyWasHandled = false;
view.KeyDownNotHandled += (s, e) => keyWasHandled = true;
var top = new Toplevel ();
top.Add (view);
Application.Begin (top);
Application.RaiseKeyDownEvent (Key.A);
Assert.False (keyWasHandled);
Assert.True (view.ApplicationCommand);
keyWasHandled = false;
Application.RaiseKeyDownEvent (Key.H);
Assert.True (view.HotKeyCommand);
Assert.False (keyWasHandled);
keyWasHandled = false;
Assert.False (view.HasFocus);
Application.RaiseKeyDownEvent (Key.F);
Assert.False (keyWasHandled);
Assert.False (view.FocusedCommand);
keyWasHandled = false;
view.CanFocus = true;
view.SetFocus ();
Assert.True (view.HasFocus);
Application.RaiseKeyDownEvent (Key.F);
Assert.True (view.FocusedCommand);
Assert.False (keyWasHandled); // Command was invoked, but wasn't handled
Assert.True (view.ApplicationCommand);
Assert.True (view.HotKeyCommand);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void KeyBinding_Negative ()
{
var view = new ScopedKeyBindingView ();
var keyWasHandled = false;
view.KeyDownNotHandled += (s, e) => keyWasHandled = true;
var top = new Toplevel ();
top.Add (view);
Application.Begin (top);
Application.RaiseKeyDownEvent (Key.Z);
Assert.False (keyWasHandled);
Assert.False (view.ApplicationCommand);
Assert.False (view.HotKeyCommand);
Assert.False (view.FocusedCommand);
keyWasHandled = false;
Assert.False (view.HasFocus);
Application.RaiseKeyDownEvent (Key.F);
Assert.False (keyWasHandled);
Assert.False (view.ApplicationCommand);
Assert.False (view.HotKeyCommand);
Assert.False (view.FocusedCommand);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void HotKey_KeyBinding ()
{
var view = new ScopedKeyBindingView ();
var keyWasHandled = false;
view.KeyDownNotHandled += (s, e) => keyWasHandled = true;
var top = new Toplevel ();
top.Add (view);
Application.Begin (top);
keyWasHandled = false;
Application.RaiseKeyDownEvent (Key.H);
Assert.True (view.HotKeyCommand);
Assert.False (keyWasHandled);
view.HotKey = KeyCode.Z;
keyWasHandled = false;
view.HotKeyCommand = false;
Application.RaiseKeyDownEvent (Key.H); // old hot key
Assert.False (keyWasHandled);
Assert.False (view.HotKeyCommand);
Application.RaiseKeyDownEvent (Key.Z); // new hot key
Assert.True (view.HotKeyCommand);
Assert.False (keyWasHandled);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void HotKey_KeyBinding_Negative ()
{
var view = new ScopedKeyBindingView ();
var keyWasHandled = false;
view.KeyDownNotHandled += (s, e) => keyWasHandled = true;
var top = new Toplevel ();
top.Add (view);
Application.Begin (top);
Application.RaiseKeyDownEvent (Key.Z);
Assert.False (keyWasHandled);
Assert.False (view.HotKeyCommand);
keyWasHandled = false;
Application.RaiseKeyDownEvent (Key.F);
Assert.False (view.HotKeyCommand);
top.Dispose ();
}
// tests that test KeyBindingScope.Focus and KeyBindingScope.HotKey (tests for KeyBindingScope.Application are in Application/KeyboardTests.cs)
public class ScopedKeyBindingView : View
{
public ScopedKeyBindingView ()
{
AddCommand (Command.Save, () => ApplicationCommand = true);
AddCommand (Command.HotKey, () => HotKeyCommand = true);
AddCommand (Command.Left, () => FocusedCommand = true);
Application.KeyBindings.Add (Key.A, this, Command.Save);
HotKey = KeyCode.H;
KeyBindings.Add (Key.F, Command.Left);
}
public bool ApplicationCommand { get; set; }
public bool FocusedCommand { get; set; }
public bool HotKeyCommand { get; set; }
}
}

View File

@@ -0,0 +1,333 @@
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
// Alias Console to MockConsole so we don't accidentally use Console
namespace Terminal.Gui.ViewTests;
public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
{
/// <summary>
/// This tests that when a new key down event is sent to the view will fire the key-down related
/// events: KeyDown and KeyDownNotHandled. Note that KeyUp is independent.
/// </summary>
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_NewKeyDownEvent_All_EventsFire (Type viewType)
{
var view = CreateInstanceIfNotGeneric (viewType);
if (view == null)
{
output.WriteLine ($"ERROR: Skipping generic view: {viewType}");
return;
}
output.WriteLine ($"Testing {viewType}");
var keyDown = false;
view.KeyDown += (s, a) =>
{
a.Handled = false; // don't handle it so the other events are called
keyDown = true;
};
var keyDownNotHandled = false;
view.KeyDownNotHandled += (s, a) =>
{
a.Handled = true;
keyDownNotHandled = true;
};
// Key.Empty is invalid, but it's used here to test that the event is fired
Assert.True (view.NewKeyDownEvent (Key.Empty)); // this will be true because the ProcessKeyDown event handled it
Assert.True (keyDown);
Assert.True (keyDownNotHandled);
view.Dispose ();
}
/// <summary>
/// This tests that when a new key up event is sent to the view the view will fire the 1 key-up related event:
/// KeyUp
/// </summary>
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_NewKeyUpEvent_All_EventsFire (Type viewType)
{
var view = CreateInstanceIfNotGeneric (viewType);
if (view == null)
{
output.WriteLine ($"ERROR: Generic view {viewType}");
return;
}
output.WriteLine ($"Testing {view.GetType ().Name}");
var keyUp = false;
view.KeyUp += (s, a) =>
{
a.Handled = true;
keyUp = true;
};
Assert.True (view.NewKeyUpEvent (Key.A)); // this will be true because the KeyUp event handled it
Assert.True (keyUp);
view.Dispose ();
}
[Theory]
[InlineData (true, false, false)]
[InlineData (true, true, false)]
[InlineData (true, true, true)]
public void NewKeyDownUpEvents_Events_Are_Raised_With_Only_Key_Modifiers (bool shift, bool alt, bool control)
{
var keyDown = false;
var keyDownNotHandled = false;
var keyUp = false;
var view = new OnNewKeyTestView ();
view.CancelVirtualMethods = false;
view.KeyDown += (s, e) =>
{
Assert.Equal (KeyCode.Null, e.KeyCode & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask);
Assert.Equal (shift, e.IsShift);
Assert.Equal (alt, e.IsAlt);
Assert.Equal (control, e.IsCtrl);
Assert.False (keyDown);
Assert.True (view.OnKeyDownCalled);
keyDown = true;
};
view.KeyDownNotHandled += (s, e) => { keyDownNotHandled = true; };
view.KeyUp += (s, e) =>
{
Assert.Equal (KeyCode.Null, e.KeyCode & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask);
Assert.Equal (shift, e.IsShift);
Assert.Equal (alt, e.IsAlt);
Assert.Equal (control, e.IsCtrl);
Assert.False (keyUp);
Assert.True (view.OnKeyUpCalled);
keyUp = true;
};
view.NewKeyDownEvent (
new (
KeyCode.Null
| (shift ? KeyCode.ShiftMask : 0)
| (alt ? KeyCode.AltMask : 0)
| (control ? KeyCode.CtrlMask : 0)
)
);
Assert.True (keyDownNotHandled);
Assert.True (view.OnKeyDownCalled);
Assert.True (view.OnProcessKeyDownCalled);
view.NewKeyUpEvent (
new (
KeyCode.Null
| (shift ? KeyCode.ShiftMask : 0)
| (alt ? KeyCode.AltMask : 0)
| (control ? KeyCode.CtrlMask : 0)
)
);
Assert.True (keyUp);
Assert.True (view.OnKeyUpCalled);
}
[Fact]
public void NewKeyDownEvent_Handled_True_Stops_Processing ()
{
var keyDown = false;
var keyDownNotHandled = false;
var view = new OnNewKeyTestView ();
Assert.True (view.CanFocus);
view.CancelVirtualMethods = false;
view.KeyDown += (s, e) =>
{
Assert.Equal (KeyCode.A, e.KeyCode);
Assert.False (keyDown);
Assert.True (view.OnKeyDownCalled);
e.Handled = true;
keyDown = true;
};
view.KeyDownNotHandled += (s, e) =>
{
Assert.Equal (KeyCode.A, e.KeyCode);
Assert.False (keyDownNotHandled);
Assert.False (view.OnProcessKeyDownCalled);
e.Handled = true;
keyDownNotHandled = true;
};
view.NewKeyDownEvent (Key.A);
Assert.True (keyDown);
Assert.False (keyDownNotHandled);
Assert.True (view.OnKeyDownCalled);
Assert.False (view.OnProcessKeyDownCalled);
}
[Fact]
public void NewKeyDownEvent_KeyDown_Handled_Stops_Processing ()
{
var view = new View ();
var keyDownNotHandled = false;
var setHandledTo = false;
view.KeyDown += (s, e) =>
{
e.Handled = setHandledTo;
Assert.Equal (setHandledTo, e.Handled);
Assert.Equal (KeyCode.N, e.KeyCode);
};
view.KeyDownNotHandled += (s, e) =>
{
keyDownNotHandled = true;
Assert.False (e.Handled);
Assert.Equal (KeyCode.N, e.KeyCode);
};
view.NewKeyDownEvent (Key.N);
Assert.True (keyDownNotHandled);
keyDownNotHandled = false;
setHandledTo = true;
view.NewKeyDownEvent (Key.N);
Assert.False (keyDownNotHandled);
}
[Fact]
public void NewKeyDownEvent_ProcessKeyDown_Handled_Stops_Processing ()
{
var keyDown = false;
var keyDownNotHandled = false;
var view = new OnNewKeyTestView ();
Assert.True (view.CanFocus);
view.CancelVirtualMethods = false;
view.KeyDown += (s, e) =>
{
Assert.Equal (KeyCode.A, e.KeyCode);
Assert.False (keyDown);
Assert.True (view.OnKeyDownCalled);
e.Handled = false;
keyDown = true;
};
view.KeyDownNotHandled += (s, e) =>
{
Assert.Equal (KeyCode.A, e.KeyCode);
Assert.False (keyDownNotHandled);
Assert.True (view.OnProcessKeyDownCalled);
e.Handled = true;
keyDownNotHandled = true;
};
view.NewKeyDownEvent (Key.A);
Assert.True (keyDown);
Assert.True (keyDownNotHandled);
Assert.True (view.OnKeyDownCalled);
Assert.True (view.OnProcessKeyDownCalled);
}
[Fact]
public void NewKeyUpEvent_KeyUp_Handled_True_Stops_Processing ()
{
var keyUp = false;
var view = new OnNewKeyTestView ();
Assert.True (view.CanFocus);
view.CancelVirtualMethods = false;
view.KeyUp += (s, e) =>
{
Assert.Equal (KeyCode.A, e.KeyCode);
Assert.False (keyUp);
Assert.False (view.OnProcessKeyDownCalled);
e.Handled = true;
keyUp = true;
};
view.NewKeyUpEvent (Key.A);
Assert.True (keyUp);
Assert.True (view.OnKeyUpCalled);
Assert.False (view.OnKeyDownCalled);
Assert.False (view.OnProcessKeyDownCalled);
}
[Theory]
[InlineData (null, null)]
[InlineData (true, true)]
[InlineData (false, false)]
public void InvokeCommands_Returns_Nullable_Properly (bool? toReturn, bool? expected)
{
var view = new KeyBindingsTestView ();
view.CommandReturns = toReturn;
bool? result = view.InvokeCommands (Key.A);
Assert.Equal (expected, result);
}
/// <summary>A view that overrides the OnKey* methods so we can test that they are called.</summary>
public class KeyBindingsTestView : View
{
public KeyBindingsTestView ()
{
CanFocus = true;
AddCommand (Command.HotKey, () => CommandReturns);
KeyBindings.Add (Key.A, Command.HotKey);
}
public bool? CommandReturns { get; set; }
}
/// <summary>A view that overrides the OnKey* methods so we can test that they are called.</summary>
public class OnNewKeyTestView : View
{
public OnNewKeyTestView () { CanFocus = true; }
public bool CancelVirtualMethods { set; private get; }
public bool OnKeyDownCalled { get; set; }
public bool OnProcessKeyDownCalled { get; set; }
public bool OnKeyUpCalled { get; set; }
public override string Text { get; set; }
protected override bool OnKeyDown (Key keyEvent)
{
OnKeyDownCalled = true;
return CancelVirtualMethods;
}
public override bool OnKeyUp (Key keyEvent)
{
OnKeyUpCalled = true;
return CancelVirtualMethods;
}
protected override bool OnKeyDownNotHandled (Key keyEvent)
{
OnProcessKeyDownCalled = true;
return CancelVirtualMethods;
}
}
}

View File

@@ -0,0 +1,28 @@
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.LayoutTests;
public class DimFillTests (ITestOutputHelper output)
{
private readonly ITestOutputHelper _output = output;
[Fact]
[AutoInitShutdown]
public void DimFill_SizedCorrectly ()
{
var view = new View { Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Single };
var top = new Toplevel ();
top.Add (view);
RunState rs = Application.Begin (top);
((FakeDriver)Application.Driver!).SetBufferSize (32, 5);
top.Layout ();
//view.SetRelativeLayout (new (0, 0, 32, 5));
Assert.Equal (32, view.Frame.Width);
Assert.Equal (5, view.Frame.Height);
top.Dispose ();
}
}

View File

@@ -0,0 +1,256 @@
using System.Globalization;
using System.Text;
using UnitTests;
using Xunit.Abstractions;
using static Terminal.Gui.Dim;
namespace Terminal.Gui.LayoutTests;
public class DimTests
{
private readonly ITestOutputHelper _output;
public DimTests (ITestOutputHelper output)
{
_output = output;
Console.OutputEncoding = Encoding.Default;
// Change current culture
var culture = CultureInfo.CreateSpecificCulture ("en-US");
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
// TODO: A new test that calls SetRelativeLayout directly is needed.
[Fact]
[AutoInitShutdown]
public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assigning_Value_To_Width_Or_Height ()
{
// Override CM
Button.DefaultShadow = ShadowStyle.None;
// Testing with the Button because it properly handles the Dim class.
Toplevel t = new ();
var w = new Window { Width = 100, Height = 100 };
var f1 = new FrameView
{
X = 0,
Y = 0,
Width = Percent (50),
Height = 5,
Title = "f1"
};
var f2 = new FrameView
{
X = Pos.Right (f1),
Y = 0,
Width = Fill (),
Height = 5,
Title = "f2"
};
var v1 = new Button
{
X = Pos.X (f1) + 2,
Y = Pos.Bottom (f1) + 2,
Width = Width (f1) - 2,
Height = Fill () - 2,
ValidatePosDim = true,
Text = "v1"
};
var v2 = new Button
{
X = Pos.X (f2) + 2,
Y = Pos.Bottom (f2) + 2,
Width = Width (f2) - 2,
Height = Fill () - 2,
ValidatePosDim = true,
Text = "v2"
};
var v3 = new Button
{
Width = Percent (10),
Height = Percent (10),
ValidatePosDim = true,
Text = "v3"
};
var v4 = new Button
{
Width = Absolute (50),
Height = Absolute (50),
ValidatePosDim = true,
Text = "v4"
};
var v5 = new Button
{
Width = Width (v1) - Width (v3),
Height = Height (v1) - Height (v3),
ValidatePosDim = true,
Text = "v5"
};
var v6 = new Button
{
X = Pos.X (f2),
Y = Pos.Bottom (f2) + 2,
Width = Percent (20, DimPercentMode.Position),
Height = Percent (20, DimPercentMode.Position),
ValidatePosDim = true,
Text = "v6"
};
w.Add (f1, f2, v1, v2, v3, v4, v5, v6);
t.Add (w);
t.Ready += (s, e) =>
{
Assert.Equal ("Absolute(100)", w.Width.ToString ());
Assert.Equal ("Absolute(100)", w.Height.ToString ());
Assert.Equal (100, w.Frame.Width);
Assert.Equal (100, w.Frame.Height);
Assert.Equal ("Absolute(5)", f1.Height.ToString ());
Assert.Equal (49, f1.Frame.Width); // 50-1=49
Assert.Equal (5, f1.Frame.Height);
Assert.Equal ("Fill(Absolute(0))", f2.Width.ToString ());
Assert.Equal ("Absolute(5)", f2.Height.ToString ());
Assert.Equal (49, f2.Frame.Width); // 50-1=49
Assert.Equal (5, f2.Frame.Height);
#if DEBUG
Assert.Equal ($"Combine(View(Width,FrameView(f1){f1.Border.Frame})-Absolute(2))", v1.Width.ToString ());
#else
Assert.Equal ($"Combine(View(Width,FrameView(){f1.Border.Frame})-Absolute(2))", v1.Width.ToString ());
#endif
Assert.Equal ("Combine(Fill(Absolute(0))-Absolute(2))", v1.Height.ToString ());
Assert.Equal (47, v1.Frame.Width); // 49-2=47
Assert.Equal (89, v1.Frame.Height); // 98-5-2-2=89
#if DEBUG
Assert.Equal (
$"Combine(View(Width,FrameView(f2){f2.Frame})-Absolute(2))",
v2.Width.ToString ()
#else
Assert.Equal (
$"Combine(View(Width,FrameView(){f2.Frame})-Absolute(2))",
v2.Width.ToString ()
#endif
);
#if DEBUG
Assert.Equal ("Combine(Fill(Absolute(0))-Absolute(2))", v2.Height.ToString ());
#else
Assert.Equal ("Combine(Fill(Absolute(0))-Absolute(2))", v2.Height.ToString ());
#endif
Assert.Equal (47, v2.Frame.Width); // 49-2=47
Assert.Equal (89, v2.Frame.Height); // 98-5-2-2=89
Assert.Equal (9, v3.Frame.Width); // 98*10%=9
Assert.Equal (9, v3.Frame.Height); // 98*10%=9
Assert.Equal ("Absolute(50)", v4.Width.ToString ());
Assert.Equal ("Absolute(50)", v4.Height.ToString ());
Assert.Equal (50, v4.Frame.Width);
Assert.Equal (50, v4.Frame.Height);
#if DEBUG
Assert.Equal ($"Combine(View(Width,Button(v1){v1.Frame})-View(Width,Button(v3){v3.Viewport}))", v5.Width.ToString ());
#else
Assert.Equal ($"Combine(View(Height,Button(){v1.Frame})-View(Height,Button(){v3.Viewport}))", v5.Height.ToString ( ));
#endif
Assert.Equal (38, v5.Frame.Width); // 47-9=38
Assert.Equal (80, v5.Frame.Height); // 89-9=80
Assert.Equal (9, v6.Frame.Width); // 47*20%=9
Assert.Equal (18, v6.Frame.Height); // 89*20%=18
w.Width = 200;
Assert.True (t.NeedsLayout);
w.Height = 200;
t.LayoutSubviews ();
Assert.Equal ("Absolute(200)", w.Width.ToString ());
Assert.Equal ("Absolute(200)", w.Height.ToString ());
Assert.Equal (200, w.Frame.Width);
Assert.Equal (200, w.Frame.Height);
f1.Text = "Frame1";
Assert.Equal (99, f1.Frame.Width); // 100-1=99
Assert.Equal (5, f1.Frame.Height);
f2.Text = "Frame2";
Assert.Equal ("Fill(Absolute(0))", f2.Width.ToString ());
Assert.Equal ("Absolute(5)", f2.Height.ToString ());
Assert.Equal (99, f2.Frame.Width); // 100-1=99
Assert.Equal (5, f2.Frame.Height);
v1.Text = "Button1";
#if DEBUG
Assert.Equal ($"Combine(View(Width,FrameView(f1){f1.Frame})-Absolute(2))", v1.Width.ToString ());
#else
Assert.Equal ($"Combine(View(Width,FrameView(){f1.Frame})-Absolute(2))", v1.Width.ToString ());
#endif
Assert.Equal ("Combine(Fill(Absolute(0))-Absolute(2))", v1.Height.ToString ());
Assert.Equal (97, v1.Frame.Width); // 99-2=97
Assert.Equal (189, v1.Frame.Height); // 198-2-7=189
v2.Text = "Button2";
#if DEBUG
Assert.Equal ($"Combine(View(Width,FrameView(f2){f2.Frame})-Absolute(2))", v2.Width.ToString ());
#else
Assert.Equal ($"Combine(View(Width,FrameView(){f2.Frame})-Absolute(2))", v2.Width.ToString ());
#endif
Assert.Equal ("Combine(Fill(Absolute(0))-Absolute(2))", v2.Height.ToString ());
Assert.Equal (97, v2.Frame.Width); // 99-2=97
Assert.Equal (189, v2.Frame.Height); // 198-2-7=189
v3.Text = "Button3";
// 198*10%=19 * Percent is related to the super-view if it isn't null otherwise the view width
Assert.Equal (19, v3.Frame.Width);
// 199*10%=19
Assert.Equal (19, v3.Frame.Height);
v4.Text = "Button4";
v4.Width = Auto (DimAutoStyle.Text);
v4.Height = Auto (DimAutoStyle.Text);
v4.Layout ();
Assert.Equal (Auto (DimAutoStyle.Text), v4.Width);
Assert.Equal (Auto (DimAutoStyle.Text), v4.Height);
Assert.Equal (11, v4.Frame.Width); // 11 is the text length and because is DimAbsolute
Assert.Equal (1, v4.Frame.Height); // 1 because is DimAbsolute
v5.Text = "Button5";
#if DEBUG
Assert.Equal ($"Combine(View(Width,Button(v1){v1.Frame})-View(Width,Button(v3){v3.Frame}))", v5.Width.ToString ());
Assert.Equal ($"Combine(View(Height,Button(v1){v1.Frame})-View(Height,Button(v3){v3.Frame}))", v5.Height.ToString ());
#else
Assert.Equal ($"Combine(View(Width,Button(){v1.Frame})-View(Width,Button(){v3.Frame}))", v5.Width.ToString ());
Assert.Equal ($"Combine(View(Height,Button(){v1.Frame})-View(Height,Button(){v3.Frame}))", v5.Height.ToString ());
#endif
Assert.Equal (78, v5.Frame.Width); // 97-9=78
Assert.Equal (170, v5.Frame.Height); // 189-19=170
v6.Text = "Button6";
Assert.Equal (19, v6.Frame.Width); // 99*20%=19
Assert.Equal (38, v6.Frame.Height); // 198-7*20=18
};
Application.Iteration += (s, a) => Application.RequestStop ();
Application.Run (t);
t.Dispose ();
}
}

View File

@@ -0,0 +1,49 @@
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.LayoutTests;
public class LayoutTests (ITestOutputHelper _output) : TestsAllViews
{
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_Layout_Does_Not_Draw (Type viewType)
{
// Required for spinner view that wants to register timeouts
Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
var view = (View)CreateInstanceIfNotGeneric (viewType);
if (view == null)
{
_output.WriteLine ($"Ignoring {viewType} - It's a Generic");
return;
}
if (view is IDesignable designable)
{
designable.EnableForDesign ();
}
var drawContentCount = 0;
view.DrawingContent += (s, e) => drawContentCount++;
var layoutStartedCount = 0;
view.SubviewLayout += (s, e) => layoutStartedCount++;
var layoutCompleteCount = 0;
view.SubviewsLaidOut += (s, e) => layoutCompleteCount++;
view.SetNeedsLayout ();
view.SetNeedsDraw();
view.Layout ();
Assert.Equal (0, drawContentCount);
Assert.Equal (1, layoutStartedCount);
Assert.Equal (1, layoutCompleteCount);
}
}

View File

@@ -0,0 +1,76 @@
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
using static Terminal.Gui.Dim;
using static Terminal.Gui.Pos;
namespace Terminal.Gui.LayoutTests;
public class PosAnchorEndTests (ITestOutputHelper output)
{
// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
// TODO: A new test that calls SetRelativeLayout directly is needed.
[Fact]
[AutoInitShutdown]
public void PosAnchorEnd_Equal_Inside_Window ()
{
var viewWidth = 10;
var viewHeight = 1;
var tv = new TextView
{
X = Pos.AnchorEnd (viewWidth), Y = Pos.AnchorEnd (viewHeight), Width = viewWidth, Height = viewHeight
};
var win = new Window ();
win.Add (tv);
Toplevel top = new ();
top.Add (win);
RunState rs = Application.Begin (top);
Assert.Equal (new (0, 0, 80, 25), top.Frame);
Assert.Equal (new (0, 0, 80, 25), win.Frame);
Assert.Equal (new (68, 22, 10, 1), tv.Frame);
Application.End (rs);
top.Dispose ();
}
//// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
//// TODO: A new test that calls SetRelativeLayout directly is needed.
//[Fact]
//[AutoInitShutdown]
//public void PosAnchorEnd_Equal_Inside_Window_With_MenuBar_And_StatusBar_On_Toplevel ()
//{
// var viewWidth = 10;
// var viewHeight = 1;
// var tv = new TextView
// {
// X = Pos.AnchorEnd (viewWidth), Y = Pos.AnchorEnd (viewHeight), Width = viewWidth, Height = viewHeight
// };
// var win = new Window ();
// win.Add (tv);
// var menu = new MenuBar ();
// var status = new StatusBar ();
// Toplevel top = new ();
// top.Add (win, menu, status);
// RunState rs = Application.Begin (top);
// Assert.Equal (new (0, 0, 80, 25), top.Frame);
// Assert.Equal (new (0, 0, 80, 1), menu.Frame);
// Assert.Equal (new (0, 24, 80, 1), status.Frame);
// Assert.Equal (new (0, 1, 80, 23), win.Frame);
// Assert.Equal (new (68, 20, 10, 1), tv.Frame);
// Application.End (rs);
// top.Dispose ();
//}
}

View File

@@ -0,0 +1,329 @@
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
using static Terminal.Gui.Dim;
using static Terminal.Gui.Pos;
namespace Terminal.Gui.LayoutTests;
public class PosCenterTests (ITestOutputHelper output)
{
private readonly ITestOutputHelper _output = output;
[Theory]
[AutoInitShutdown]
[InlineData (1)]
[InlineData (2)]
[InlineData (3)]
[InlineData (4)]
[InlineData (5)]
[InlineData (6)]
[InlineData (7)]
[InlineData (8)]
[InlineData (9)]
[InlineData (10)]
public void PosCenter_SubView_85_Percent_Height (int height)
{
var win = new Window { Width = Fill (), Height = Fill () };
var subview = new Window
{
X = Center (), Y = Center (), Width = Dim.Percent (85), Height = Dim.Percent (85)
};
win.Add (subview);
RunState rs = Application.Begin (win);
var firstIteration = false;
((FakeDriver)Application.Driver!).SetBufferSize (20, height);
Application.RunIteration (ref rs, firstIteration);
var expected = string.Empty;
switch (height)
{
case 1:
//Assert.Equal (new (0, 0, 17, 0), subview.Frame);
expected = @"
────────────────────";
break;
case 2:
//Assert.Equal (new (0, 0, 17, 1), subview.Frame);
expected = @"
┌──────────────────┐
└──────────────────┘
";
break;
case 3:
//Assert.Equal (new (0, 0, 17, 2), subview.Frame);
expected = @"
┌──────────────────┐
│ │
└──────────────────┘
";
break;
case 4:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌──────────────────┐
│ ─────────────── │
│ │
└──────────────────┘";
break;
case 5:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌──────────────────┐
│ ┌─────────────┐ │
│ └─────────────┘ │
│ │
└──────────────────┘";
break;
case 6:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌──────────────────┐
│ ┌─────────────┐ │
│ │ │ │
│ └─────────────┘ │
│ │
└──────────────────┘";
break;
case 7:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌──────────────────┐
│ ┌─────────────┐ │
│ │ │ │
│ │ │ │
│ └─────────────┘ │
│ │
└──────────────────┘";
break;
case 8:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌──────────────────┐
│ ┌─────────────┐ │
│ │ │ │
│ │ │ │
│ │ │ │
│ └─────────────┘ │
│ │
└──────────────────┘";
break;
case 9:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌──────────────────┐
│ │
│ ┌─────────────┐ │
│ │ │ │
│ │ │ │
│ │ │ │
│ └─────────────┘ │
│ │
└──────────────────┘";
break;
case 10:
//Assert.Equal (new (0, 0, 17, 3), subview.Frame);
expected = @"
┌──────────────────┐
│ │
│ ┌─────────────┐ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ └─────────────┘ │
│ │
└──────────────────┘"
;
break;
}
_ = DriverAssert.AssertDriverContentsWithFrameAre (expected, _output);
Application.End (rs);
win.Dispose ();
}
[Theory]
[AutoInitShutdown]
[InlineData (1)]
[InlineData (2)]
[InlineData (3)]
[InlineData (4)]
[InlineData (5)]
[InlineData (6)]
[InlineData (7)]
[InlineData (8)]
[InlineData (9)]
[InlineData (10)]
public void PosCenter_SubView_85_Percent_Width (int width)
{
var win = new Window { Width = Fill (), Height = Fill () };
var subview = new Window
{
X = Center (), Y = Center (), Width = Dim.Percent (85), Height = Dim.Percent (85)
};
win.Add (subview);
RunState rs = Application.Begin (win);
var firstIteration = false;
((FakeDriver)Application.Driver!).SetBufferSize (width, 7);
Application.RunIteration (ref rs, firstIteration);
var expected = string.Empty;
switch (width)
{
case 1:
Assert.Equal (new (0, 0, 0, 4), subview.Frame);
expected = @"
│";
break;
case 2:
Assert.Equal (new (0, 0, 0, 4), subview.Frame);
expected = @"
┌┐
││
││
││
││
││
└┘";
break;
case 3:
Assert.Equal (new (0, 0, 0, 4), subview.Frame);
expected = @"
┌─┐
│ │
│ │
│ │
│ │
│ │
└─┘";
break;
case 4:
Assert.Equal (new (0, 0, 1, 4), subview.Frame);
expected = @"
┌──┐
││ │
││ │
││ │
││ │
│ │
└──┘";
break;
case 5:
Assert.Equal (new (0, 0, 2, 4), subview.Frame);
expected = @"
┌───┐
│┌┐ │
│││ │
│││ │
│└┘ │
│ │
└───┘";
break;
case 6:
Assert.Equal (new (0, 0, 3, 4), subview.Frame);
expected = @"
┌────┐
│┌─┐ │
││ │ │
││ │ │
│└─┘ │
│ │
└────┘";
break;
case 7:
Assert.Equal (new (0, 0, 4, 4), subview.Frame);
expected = @"
┌─────┐
│┌──┐ │
││ │ │
││ │ │
│└──┘ │
│ │
└─────┘";
break;
case 8:
Assert.Equal (new (0, 0, 5, 4), subview.Frame);
expected = @"
┌──────┐
│┌───┐ │
││ │ │
││ │ │
│└───┘ │
│ │
└──────┘";
break;
case 9:
Assert.Equal (new (1, 0, 5, 4), subview.Frame);
expected = @"
┌───────┐
│ ┌───┐ │
│ │ │ │
│ │ │ │
│ └───┘ │
│ │
└───────┘";
break;
case 10:
Assert.Equal (new (1, 0, 6, 4), subview.Frame);
expected = @"
┌────────┐
│ ┌────┐ │
│ │ │ │
│ │ │ │
│ └────┘ │
│ │
└────────┘"
;
break;
}
_ = DriverAssert.AssertDriverContentsWithFrameAre (expected, _output);
Application.End (rs);
win.Dispose ();
}
}

View File

@@ -0,0 +1,122 @@
using Microsoft.VisualStudio.TestPlatform.Utilities;
using UnitTests;
using Xunit.Abstractions;
using static Terminal.Gui.Dim;
using static Terminal.Gui.Pos;
namespace Terminal.Gui.LayoutTests;
public class PosCombineTests (ITestOutputHelper output)
{
private readonly ITestOutputHelper _output = output;
// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
// TODO: A new test that calls SetRelativeLayout directly is needed.
[Fact]
[TestRespondersDisposed]
public void PosCombine_Will_Throws ()
{
Application.Init (new FakeDriver ());
Toplevel t = new ();
var w = new Window { X = Pos.Left (t) + 2, Y = Pos.Top (t) + 2 };
var f = new FrameView ();
var v1 = new View { X = Pos.Left (w) + 2, Y = Pos.Top (w) + 2 };
var v2 = new View { X = Pos.Left (v1) + 2, Y = Pos.Top (v1) + 2 };
f.Add (v1); // v2 not added
w.Add (f);
t.Add (w);
f.X = Pos.X (v2) - Pos.X (v1);
f.Y = Pos.Y (v2) - Pos.Y (v1);
Assert.Throws<LayoutException> (() => Application.Run (t));
t.Dispose ();
Application.Shutdown ();
v2.Dispose ();
}
[Fact]
[SetupFakeDriver]
public void PosCombine_DimCombine_View_With_SubViews ()
{
Application.Top = new Toplevel () { Width = 80, Height = 25 };
var win1 = new Window { Id = "win1", Width = 20, Height = 10 };
var view1 = new View
{
Text = "view1",
Width = Auto (DimAutoStyle.Text),
Height = Auto (DimAutoStyle.Text)
};
var win2 = new Window { Id = "win2", Y = Pos.Bottom (view1) + 1, Width = 10, Height = 3 };
var view2 = new View { Id = "view2", Width = Dim.Fill (), Height = 1, CanFocus = true };
//var clicked = false;
//view2.MouseClick += (sender, e) => clicked = true;
var view3 = new View { Id = "view3", Width = Dim.Fill (1), Height = 1, CanFocus = true };
view2.Add (view3);
win2.Add (view2);
win1.Add (view1, win2);
Application.Top.Add (win1);
Application.Top.Layout ();
Assert.Equal (new Rectangle (0, 0, 80, 25), Application.Top.Frame);
Assert.Equal (new Rectangle (0, 0, 5, 1), view1.Frame);
Assert.Equal (new Rectangle (0, 0, 20, 10), win1.Frame);
Assert.Equal (new Rectangle (0, 2, 10, 3), win2.Frame);
Assert.Equal (new Rectangle (0, 0, 8, 1), view2.Frame);
Assert.Equal (new Rectangle (0, 0, 7, 1), view3.Frame);
var foundView = View.GetViewsUnderMouse (new Point(9, 4)).LastOrDefault ();
Assert.Equal (foundView, view2);
Application.Top.Dispose ();
Application.ResetState (ignoreDisposed: true);
}
[Fact]
public void PosCombine_Refs_SuperView_Throws ()
{
Application.Init (new FakeDriver ());
var top = new Toplevel ();
var w = new Window { X = Pos.Left (top) + 2, Y = Pos.Top (top) + 2 };
var f = new FrameView ();
var v1 = new View { X = Pos.Left (w) + 2, Y = Pos.Top (w) + 2 };
var v2 = new View { X = Pos.Left (v1) + 2, Y = Pos.Top (v1) + 2 };
f.Add (v1, v2);
w.Add (f);
top.Add (w);
Application.Begin (top);
f.X = Pos.X (Application.Top) + Pos.X (v2) - Pos.X (v1);
f.Y = Pos.Y (Application.Top) + Pos.Y (v2) - Pos.Y (v1);
Application.Top.SubviewsLaidOut += (s, e) =>
{
Assert.Equal (0, Application.Top.Frame.X);
Assert.Equal (0, Application.Top.Frame.Y);
Assert.Equal (2, w.Frame.X);
Assert.Equal (2, w.Frame.Y);
Assert.Equal (2, f.Frame.X);
Assert.Equal (2, f.Frame.Y);
Assert.Equal (4, v1.Frame.X);
Assert.Equal (4, v1.Frame.Y);
Assert.Equal (6, v2.Frame.X);
Assert.Equal (6, v2.Frame.Y);
};
Application.Iteration += (s, a) => Application.RequestStop ();
Assert.Throws<LayoutException> (() => Application.Run ());
top.Dispose ();
Application.ResetState (ignoreDisposed: true);
}
}

View File

@@ -0,0 +1,332 @@
using UnitTests;
using Xunit.Abstractions;
using static Terminal.Gui.Dim;
using static Terminal.Gui.Pos;
namespace Terminal.Gui.LayoutTests;
public class PosTests ()
{
[Fact]
public void
Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
{
Application.Init (new FakeDriver ());
Toplevel t = new ();
var w = new Window { X = Pos.Left (t) + 2, Y = Pos.Absolute (2) };
var v = new View { X = Pos.Center (), Y = Pos.Percent (10) };
w.Add (v);
t.Add (w);
t.Ready += (s, e) =>
{
v.Frame = new Rectangle (2, 2, 10, 10);
Assert.Equal (2, v.X = 2);
Assert.Equal (2, v.Y = 2);
};
Application.Iteration += (s, a) => Application.RequestStop ();
Application.Run (t);
t.Dispose ();
Application.Shutdown ();
}
// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
// TODO: A new test that calls SetRelativeLayout directly is needed.
[Fact]
[TestRespondersDisposed]
public void PosCombine_WHY_Throws ()
{
Application.Init (new FakeDriver ());
Toplevel t = new Toplevel ();
var w = new Window { X = Pos.Left (t) + 2, Y = Pos.Top (t) + 2 };
var f = new FrameView ();
var v1 = new View { X = Pos.Left (w) + 2, Y = Pos.Top (w) + 2 };
var v2 = new View { X = Pos.Left (v1) + 2, Y = Pos.Top (v1) + 2 };
f.Add (v1); // v2 not added
w.Add (f);
t.Add (w);
f.X = Pos.X (v2) - Pos.X (v1);
f.Y = Pos.Y (v2) - Pos.Y (v1);
Assert.Throws<LayoutException> (() => Application.Run (t));
t.Dispose ();
Application.Shutdown ();
v2.Dispose ();
}
// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
// TODO: A new test that calls SetRelativeLayout directly is needed.
// See: https://github.com/gui-cs/Terminal.Gui/issues/504
[Fact]
[TestRespondersDisposed]
public void LeftTopBottomRight_Win_ShouldNotThrow ()
{
// Test cases:
(Toplevel top, Window win, Button button) app = Setup ();
app.button.Y = Pos.Left (app.win);
RunState rs = Application.Begin (app.top);
// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
Application.RunLoop (rs);
Cleanup (rs);
app = Setup ();
app.button.Y = Pos.X (app.win);
rs = Application.Begin (app.top);
// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
Application.RunLoop (rs);
Cleanup (rs);
app = Setup ();
app.button.Y = Pos.Top (app.win);
rs = Application.Begin (app.top);
// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
Application.RunLoop (rs);
Cleanup (rs);
app = Setup ();
app.button.Y = Pos.Y (app.win);
rs = Application.Begin (app.top);
// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
Application.RunLoop (rs);
Cleanup (rs);
app = Setup ();
app.button.Y = Pos.Bottom (app.win);
rs = Application.Begin (app.top);
// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
Application.RunLoop (rs);
Cleanup (rs);
app = Setup ();
app.button.Y = Pos.Right (app.win);
rs = Application.Begin (app.top);
// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
Application.RunLoop (rs);
Cleanup (rs);
return;
void Cleanup (RunState rs)
{
// Cleanup
Application.End (rs);
Application.Top.Dispose ();
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
// Setup Fake driver
(Toplevel top, Window win, Button button) Setup ()
{
Application.Init (new FakeDriver ());
Application.Iteration += (s, a) => { Application.RequestStop (); };
var win = new Window { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
var top = new Toplevel ();
top.Add (win);
var button = new Button { X = Pos.Center (), Text = "button" };
win.Add (button);
return (top, win, button);
}
}
// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
// TODO: A new test that calls SetRelativeLayout directly is needed.
[Fact]
[TestRespondersDisposed]
public void Pos_Add_Operator ()
{
Application.Init (new FakeDriver ());
Toplevel top = new ();
var view = new View { X = 0, Y = 0, Width = 20, Height = 20 };
var field = new TextField { X = 0, Y = 0, Width = 20 };
var count = 0;
field.KeyDown += (s, k) =>
{
if (k.KeyCode == KeyCode.Enter)
{
field.Text = $"View {count}";
var view2 = new View { X = 0, Y = field.Y, Width = 20, Text = field.Text };
view.Add (view2);
Assert.Equal ($"View {count}", view2.Text);
Assert.Equal ($"Absolute({count})", view2.Y.ToString ());
Assert.Equal ($"Absolute({count})", field.Y.ToString ());
field.Y += 1;
count++;
Assert.Equal ($"Absolute({count})", field.Y.ToString ());
}
};
Application.Iteration += (s, a) =>
{
while (count < 20)
{
field.NewKeyDownEvent (Key.Enter);
}
Application.RequestStop ();
};
var win = new Window ();
win.Add (view);
win.Add (field);
top.Add (win);
Application.Run (top);
Assert.Equal (20, count);
top.Dispose ();
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
// TODO: A new test that calls SetRelativeLayout directly is needed.
[Fact]
[TestRespondersDisposed]
public void Pos_Subtract_Operator ()
{
Application.Init (new FakeDriver ());
Toplevel top = new ();
var view = new View { X = 0, Y = 0, Width = 20, Height = 20 };
var field = new TextField { X = 0, Y = 0, Width = 20 };
var count = 20;
List<View> listViews = new ();
for (var i = 0; i < count; i++)
{
field.Text = $"View {i}";
var view2 = new View { X = 0, Y = field.Y, Width = 20, Text = field.Text };
view.Add (view2);
Assert.Equal ($"View {i}", view2.Text);
Assert.Equal ($"Absolute({i})", field.Y.ToString ());
listViews.Add (view2);
Assert.Equal ($"Absolute({i})", field.Y.ToString ());
field.Y += 1;
Assert.Equal ($"Absolute({i + 1})", field.Y.ToString ());
}
field.KeyDown += (s, k) =>
{
if (k.KeyCode == KeyCode.Enter)
{
Assert.Equal ($"View {count - 1}", listViews [count - 1].Text);
view.Remove (listViews [count - 1]);
listViews [count - 1].Dispose ();
Assert.Equal ($"Absolute({count})", field.Y.ToString ());
field.Y -= 1;
count--;
Assert.Equal ($"Absolute({count})", field.Y.ToString ());
}
};
Application.Iteration += (s, a) =>
{
while (count > 0)
{
field.NewKeyDownEvent (Key.Enter);
}
Application.RequestStop ();
};
var win = new Window ();
win.Add (view);
win.Add (field);
top.Add (win);
Application.Run (top);
Assert.Equal (0, count);
top.Dispose ();
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
// TODO: A new test that calls SetRelativeLayout directly is needed.
[Fact]
public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null ()
{
Application.Init (new FakeDriver ());
Toplevel t = new ();
var w = new Window { X = 1, Y = 2, Width = 3, Height = 5 };
t.Add (w);
t.Ready += (s, e) =>
{
Assert.Equal (2, w.X = 2);
Assert.Equal (2, w.Y = 2);
};
Application.Iteration += (s, a) => Application.RequestStop ();
Application.Run (t);
t.Dispose ();
Application.Shutdown ();
}
// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
// TODO: A new test that calls SetRelativeLayout directly is needed.
[Fact]
public void Validation_Does_Not_Throw_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null ()
{
Application.Init (new FakeDriver ());
Toplevel t = new Toplevel ();
var w = new Window { X = 1, Y = 2, Width = 3, Height = 5 };
t.Add (w);
t.Ready += (s, e) =>
{
Assert.Equal (2, w.X = 2);
Assert.Equal (2, w.Y = 2);
};
Application.Iteration += (s, a) => Application.RequestStop ();
Application.Run (t);
t.Dispose ();
Application.Shutdown ();
}
}

View File

@@ -0,0 +1,78 @@
using UnitTests;
using Xunit.Abstractions;
using static Terminal.Gui.Pos;
namespace Terminal.Gui.LayoutTests;
public class PosViewTests (ITestOutputHelper output)
{
private readonly ITestOutputHelper _output = output;
// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
// TODO: A new test that calls SetRelativeLayout directly is needed.
[Fact]
[TestRespondersDisposed]
public void Subtract_Operator ()
{
Application.Init (new FakeDriver ());
var top = new Toplevel ();
var view = new View { X = 0, Y = 0, Width = 20, Height = 20 };
var field = new TextField { X = 0, Y = 0, Width = 20 };
var count = 20;
List<View> listViews = new ();
for (var i = 0; i < count; i++)
{
field.Text = $"View {i}";
var view2 = new View { X = 0, Y = field.Y, Width = 20, Text = field.Text };
view.Add (view2);
Assert.Equal ($"View {i}", view2.Text);
Assert.Equal ($"Absolute({i})", field.Y.ToString ());
listViews.Add (view2);
Assert.Equal ($"Absolute({i})", field.Y.ToString ());
field.Y += 1;
Assert.Equal ($"Absolute({i + 1})", field.Y.ToString ());
}
field.KeyDown += (s, k) =>
{
if (k.KeyCode == KeyCode.Enter)
{
Assert.Equal ($"View {count - 1}", listViews [count - 1].Text);
view.Remove (listViews [count - 1]);
listViews [count - 1].Dispose ();
Assert.Equal ($"Absolute({count})", field.Y.ToString ());
field.Y -= 1;
count--;
Assert.Equal ($"Absolute({count})", field.Y.ToString ());
}
};
Application.Iteration += (s, a) =>
{
while (count > 0)
{
field.NewKeyDownEvent (new (KeyCode.Enter));
}
Application.RequestStop ();
};
var win = new Window ();
win.Add (view);
win.Add (field);
top.Add (win);
Application.Run (top);
top.Dispose ();
Assert.Equal (0, count);
// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
}

View File

@@ -0,0 +1,41 @@
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.LayoutTests;
public class SetLayoutTests (ITestOutputHelper output)
{
private readonly ITestOutputHelper _output = output;
[Fact]
[AutoInitShutdown]
public void Screen_Size_Change_Causes_Layout ()
{
Application.Top = new ();
var view = new View
{
X = 3,
Y = 2,
Width = 10,
Height = 1,
Text = "0123456789"
};
Application.Top.Add (view);
var rs = Application.Begin (Application.Top);
Assert.Equal (new (0, 0, 80, 25), new Rectangle (0, 0, View.Driver.Cols, View.Driver.Rows));
Assert.Equal (new (0, 0, View.Driver.Cols, View.Driver.Rows), Application.Top.Frame);
Assert.Equal (new (0, 0, 80, 25), Application.Top.Frame);
((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
Assert.Equal (new (0, 0, View.Driver.Cols, View.Driver.Rows), Application.Top.Frame);
Assert.Equal (new (0, 0, 20, 10), Application.Top.Frame);
Application.End (rs);
}
}

View File

@@ -0,0 +1,805 @@
#nullable enable
namespace Terminal.Gui.ViewMouseTests;
[Trait ("Category", "Input")]
public class GetViewsUnderMouseTests
{
[Theory]
[InlineData (0, 0, 0, 0, 0, -1, -1, null)]
[InlineData (0, 0, 0, 0, 0, 0, 0, typeof (Toplevel))]
[InlineData (0, 0, 0, 0, 0, 1, 1, typeof (Toplevel))]
[InlineData (0, 0, 0, 0, 0, 4, 4, typeof (Toplevel))]
[InlineData (0, 0, 0, 0, 0, 9, 9, typeof (Toplevel))]
[InlineData (0, 0, 0, 0, 0, 10, 10, null)]
[InlineData (1, 1, 0, 0, 0, -1, -1, null)]
[InlineData (1, 1, 0, 0, 0, 0, 0, null)]
[InlineData (1, 1, 0, 0, 0, 1, 1, typeof (Toplevel))]
[InlineData (1, 1, 0, 0, 0, 4, 4, typeof (Toplevel))]
[InlineData (1, 1, 0, 0, 0, 9, 9, typeof (Toplevel))]
[InlineData (1, 1, 0, 0, 0, 10, 10, typeof (Toplevel))]
[InlineData (0, 0, 1, 0, 0, -1, -1, null)]
[InlineData (0, 0, 1, 0, 0, 0, 0, typeof (Margin))]
[InlineData (0, 0, 1, 0, 0, 1, 1, typeof (Toplevel))]
[InlineData (0, 0, 1, 0, 0, 4, 4, typeof (Toplevel))]
[InlineData (0, 0, 1, 0, 0, 9, 9, typeof (Margin))]
[InlineData (0, 0, 1, 0, 0, 10, 10, null)]
[InlineData (0, 0, 1, 1, 0, -1, -1, null)]
[InlineData (0, 0, 1, 1, 0, 0, 0, typeof (Margin))]
[InlineData (0, 0, 1, 1, 0, 1, 1, typeof (Border))]
[InlineData (0, 0, 1, 1, 0, 4, 4, typeof (Toplevel))]
[InlineData (0, 0, 1, 1, 0, 9, 9, typeof (Margin))]
[InlineData (0, 0, 1, 1, 0, 10, 10, null)]
[InlineData (0, 0, 1, 1, 1, -1, -1, null)]
[InlineData (0, 0, 1, 1, 1, 0, 0, typeof (Margin))]
[InlineData (0, 0, 1, 1, 1, 1, 1, typeof (Border))]
[InlineData (0, 0, 1, 1, 1, 2, 2, typeof (Padding))]
[InlineData (0, 0, 1, 1, 1, 4, 4, typeof (Toplevel))]
[InlineData (0, 0, 1, 1, 1, 9, 9, typeof (Margin))]
[InlineData (0, 0, 1, 1, 1, 10, 10, null)]
[InlineData (1, 1, 1, 0, 0, -1, -1, null)]
[InlineData (1, 1, 1, 0, 0, 0, 0, null)]
[InlineData (1, 1, 1, 0, 0, 1, 1, typeof (Margin))]
[InlineData (1, 1, 1, 0, 0, 4, 4, typeof (Toplevel))]
[InlineData (1, 1, 1, 0, 0, 9, 9, typeof (Toplevel))]
[InlineData (1, 1, 1, 0, 0, 10, 10, typeof (Margin))]
[InlineData (1, 1, 1, 1, 0, -1, -1, null)]
[InlineData (1, 1, 1, 1, 0, 0, 0, null)]
[InlineData (1, 1, 1, 1, 0, 1, 1, typeof (Margin))]
[InlineData (1, 1, 1, 1, 0, 4, 4, typeof (Toplevel))]
[InlineData (1, 1, 1, 1, 0, 9, 9, typeof (Border))]
[InlineData (1, 1, 1, 1, 0, 10, 10, typeof (Margin))]
[InlineData (1, 1, 1, 1, 1, -1, -1, null)]
[InlineData (1, 1, 1, 1, 1, 0, 0, null)]
[InlineData (1, 1, 1, 1, 1, 1, 1, typeof (Margin))]
[InlineData (1, 1, 1, 1, 1, 2, 2, typeof (Border))]
[InlineData (1, 1, 1, 1, 1, 3, 3, typeof (Padding))]
[InlineData (1, 1, 1, 1, 1, 4, 4, typeof (Toplevel))]
[InlineData (1, 1, 1, 1, 1, 8, 8, typeof (Padding))]
[InlineData (1, 1, 1, 1, 1, 9, 9, typeof (Border))]
[InlineData (1, 1, 1, 1, 1, 10, 10, typeof (Margin))]
public void GetViewsUnderMouse_Top_Adornments_Returns_Correct_View (
int frameX,
int frameY,
int marginThickness,
int borderThickness,
int paddingThickness,
int testX,
int testY,
Type? expectedViewType
)
{
// Arrange
Application.Top = new ()
{
Frame = new (frameX, frameY, 10, 10)
};
Application.Top.Margin.Thickness = new (marginThickness);
Application.Top.Border.Thickness = new (borderThickness);
Application.Top.Padding.Thickness = new (paddingThickness);
var location = new Point (testX, testY);
// Act
List<View?> viewsUnderMouse = View.GetViewsUnderMouse (location);
// Assert
if (expectedViewType == null)
{
Assert.Empty (viewsUnderMouse);
}
else
{
Assert.Contains (viewsUnderMouse, v => v?.GetType () == expectedViewType);
}
Application.Top.Dispose ();
Application.ResetState (true);
}
[Theory]
[InlineData (0, 0)]
[InlineData (1, 1)]
[InlineData (2, 2)]
public void GetViewsUnderMouse_Returns_Top_If_No_SubViews (int testX, int testY)
{
// Arrange
Application.Top = new ()
{
Frame = new (0, 0, 10, 10)
};
var location = new Point (testX, testY);
// Act
List<View?> viewsUnderMouse = View.GetViewsUnderMouse (location);
// Assert
Assert.Contains (viewsUnderMouse, v => v == Application.Top);
Application.Top.Dispose ();
Application.ResetState (true);
}
[Theory]
[InlineData (0, 0)]
[InlineData (2, 1)]
[InlineData (20, 20)]
public void GetViewsUnderMouse_Returns_Null_If_No_SubViews_Coords_Outside (int testX, int testY)
{
// Arrange
var view = new View
{
Frame = new (0, 0, 10, 10)
};
var location = new Point (testX, testY);
// Act
List<View?> viewsUnderMouse = View.GetViewsUnderMouse (location);
// Assert
Assert.Empty (viewsUnderMouse);
}
[Theory]
[InlineData (0, 0)]
[InlineData (2, 1)]
[InlineData (20, 20)]
public void GetViewsUnderMouse_Returns_Null_If_Start_Not_Visible (int testX, int testY)
{
// Arrange
var view = new View
{
Frame = new (0, 0, 10, 10),
Visible = false
};
var location = new Point (testX, testY);
// Act
List<View?> viewsUnderMouse = View.GetViewsUnderMouse (location);
// Assert
Assert.Empty (viewsUnderMouse);
}
[Theory]
[InlineData (0, 0, false)]
[InlineData (1, 1, true)]
[InlineData (9, 9, false)]
[InlineData (10, 10, false)]
[InlineData (6, 7, true)]
[InlineData (1, 2, true)]
[InlineData (5, 6, true)]
public void GetViewsUnderMouse_Returns_Correct_If_SubViews (int testX, int testY, bool expected)
{
// Arrange
Application.Top = new ()
{
Frame = new (0, 0, 10, 10)
};
var subView = new View
{
Frame = new (1, 1, 8, 8)
};
Application.Top.Add (subView);
var location = new Point (testX, testY);
// Act
List<View?> viewsUnderMouse = View.GetViewsUnderMouse (location);
// Assert
if (expected)
{
Assert.Contains (viewsUnderMouse, v => v == subView);
}
else
{
Assert.DoesNotContain (viewsUnderMouse, v => v == subView);
}
Application.Top.Dispose ();
Application.ResetState (true);
}
[Theory]
[InlineData (0, 0, 0, 0, 0, -1, -1, null)]
[InlineData (0, 0, 0, 0, 0, 0, 0, typeof (View))]
[InlineData (0, 0, 0, 0, 0, 1, 1, typeof (View))]
[InlineData (0, 0, 0, 0, 0, 4, 4, typeof (View))]
[InlineData (0, 0, 0, 0, 0, 9, 9, typeof (View))]
[InlineData (0, 0, 0, 0, 0, 10, 10, null)]
[InlineData (1, 1, 0, 0, 0, -1, -1, null)]
[InlineData (1, 1, 0, 0, 0, 0, 0, null)]
[InlineData (1, 1, 0, 0, 0, 1, 1, typeof (View))]
[InlineData (1, 1, 0, 0, 0, 4, 4, typeof (View))]
[InlineData (1, 1, 0, 0, 0, 9, 9, typeof (View))]
[InlineData (1, 1, 0, 0, 0, 10, 10, typeof (View))]
[InlineData (0, 0, 1, 0, 0, -1, -1, null)]
[InlineData (0, 0, 1, 0, 0, 0, 0, typeof (Margin))]
[InlineData (0, 0, 1, 0, 0, 1, 1, typeof (View))]
[InlineData (0, 0, 1, 0, 0, 4, 4, typeof (View))]
[InlineData (0, 0, 1, 0, 0, 9, 9, typeof (Margin))]
[InlineData (0, 0, 1, 0, 0, 10, 10, null)]
[InlineData (0, 0, 1, 1, 0, -1, -1, null)]
[InlineData (0, 0, 1, 1, 0, 0, 0, typeof (Margin))]
[InlineData (0, 0, 1, 1, 0, 1, 1, typeof (Border))]
[InlineData (0, 0, 1, 1, 0, 4, 4, typeof (View))]
[InlineData (0, 0, 1, 1, 0, 9, 9, typeof (Margin))]
[InlineData (0, 0, 1, 1, 0, 10, 10, null)]
[InlineData (0, 0, 1, 1, 1, -1, -1, null)]
[InlineData (0, 0, 1, 1, 1, 0, 0, typeof (Margin))]
[InlineData (0, 0, 1, 1, 1, 1, 1, typeof (Border))]
[InlineData (0, 0, 1, 1, 1, 2, 2, typeof (Padding))]
[InlineData (0, 0, 1, 1, 1, 4, 4, typeof (View))]
[InlineData (0, 0, 1, 1, 1, 9, 9, typeof (Margin))]
[InlineData (0, 0, 1, 1, 1, 10, 10, null)]
[InlineData (1, 1, 1, 0, 0, -1, -1, null)]
[InlineData (1, 1, 1, 0, 0, 0, 0, null)]
[InlineData (1, 1, 1, 0, 0, 1, 1, typeof (Margin))]
[InlineData (1, 1, 1, 0, 0, 4, 4, typeof (View))]
[InlineData (1, 1, 1, 0, 0, 9, 9, typeof (View))]
[InlineData (1, 1, 1, 0, 0, 10, 10, typeof (Margin))]
[InlineData (1, 1, 1, 1, 0, -1, -1, null)]
[InlineData (1, 1, 1, 1, 0, 0, 0, null)]
[InlineData (1, 1, 1, 1, 0, 1, 1, typeof (Margin))]
[InlineData (1, 1, 1, 1, 0, 4, 4, typeof (View))]
[InlineData (1, 1, 1, 1, 0, 9, 9, typeof (Border))]
[InlineData (1, 1, 1, 1, 0, 10, 10, typeof (Margin))]
[InlineData (1, 1, 1, 1, 1, -1, -1, null)]
[InlineData (1, 1, 1, 1, 1, 0, 0, null)]
[InlineData (1, 1, 1, 1, 1, 1, 1, typeof (Margin))]
[InlineData (1, 1, 1, 1, 1, 2, 2, typeof (Border))]
[InlineData (1, 1, 1, 1, 1, 3, 3, typeof (Padding))]
[InlineData (1, 1, 1, 1, 1, 4, 4, typeof (View))]
[InlineData (1, 1, 1, 1, 1, 8, 8, typeof (Padding))]
[InlineData (1, 1, 1, 1, 1, 9, 9, typeof (Border))]
[InlineData (1, 1, 1, 1, 1, 10, 10, typeof (Margin))]
public void Contains (
int frameX,
int frameY,
int marginThickness,
int borderThickness,
int paddingThickness,
int testX,
int testY,
Type? expectedAdornmentType
)
{
var view = new View
{
X = frameX, Y = frameY,
Width = 10, Height = 10
};
view.Margin.Thickness = new (marginThickness);
view.Border.Thickness = new (borderThickness);
view.Padding.Thickness = new (paddingThickness);
Type? containedType = null;
if (view.Contains (new (testX, testY)))
{
containedType = view.GetType ();
}
if (view.Margin.Contains (new (testX, testY)))
{
containedType = view.Margin.GetType ();
}
if (view.Border.Contains (new (testX, testY)))
{
containedType = view.Border.GetType ();
}
if (view.Padding.Contains (new (testX, testY)))
{
containedType = view.Padding.GetType ();
}
Assert.Equal (expectedAdornmentType, containedType);
}
// Test that GetViewsUnderMouse returns the correct view if the start view has no subviews
[Theory]
[InlineData (0, 0)]
[InlineData (1, 1)]
[InlineData (2, 2)]
public void Returns_Start_If_No_SubViews (int testX, int testY)
{
Application.Top = new ()
{
Width = 10, Height = 10
};
Assert.Same (Application.Top, View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ());
Application.Top.Dispose ();
Application.ResetState (true);
}
// Test that GetViewsUnderMouse returns null if the start view has no subviews and coords are outside the view
[Theory]
[InlineData (0, 0)]
[InlineData (2, 1)]
[InlineData (20, 20)]
public void Returns_Null_If_No_SubViews_Coords_Outside (int testX, int testY)
{
Application.Top = new ()
{
X = 1, Y = 2,
Width = 10, Height = 10
};
Assert.Null (View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ());
Application.Top.Dispose ();
Application.ResetState (true);
}
[Theory]
[InlineData (0, 0)]
[InlineData (2, 1)]
[InlineData (20, 20)]
public void Returns_Null_If_Start_Not_Visible (int testX, int testY)
{
Application.Top = new ()
{
X = 1, Y = 2,
Width = 10, Height = 10,
Visible = false
};
Assert.Null (View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ());
Application.Top.Dispose ();
Application.ResetState (true);
}
// Test that GetViewsUnderMouse returns the correct view if the start view has subviews
[Theory]
[InlineData (0, 0, false)]
[InlineData (1, 1, false)]
[InlineData (9, 9, false)]
[InlineData (10, 10, false)]
[InlineData (6, 7, false)]
[InlineData (1, 2, true)]
[InlineData (5, 6, true)]
public void Returns_Correct_If_SubViews (int testX, int testY, bool expectedSubViewFound)
{
Application.Top = new ()
{
Width = 10, Height = 10
};
var subview = new View
{
X = 1, Y = 2,
Width = 5, Height = 5
};
Application.Top.Add (subview);
View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ();
Assert.Equal (expectedSubViewFound, found == subview);
Application.Top.Dispose ();
Application.ResetState (true);
}
[Theory]
[InlineData (0, 0, false)]
[InlineData (1, 1, false)]
[InlineData (9, 9, false)]
[InlineData (10, 10, false)]
[InlineData (6, 7, false)]
[InlineData (1, 2, false)]
[InlineData (5, 6, false)]
public void Returns_Null_If_SubView_NotVisible (int testX, int testY, bool expectedSubViewFound)
{
Application.Top = new ()
{
Width = 10, Height = 10
};
var subview = new View
{
X = 1, Y = 2,
Width = 5, Height = 5,
Visible = false
};
Application.Top.Add (subview);
View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ();
Assert.Equal (expectedSubViewFound, found == subview);
Application.Top.Dispose ();
Application.ResetState (true);
}
[Theory]
[InlineData (0, 0, false)]
[InlineData (1, 1, false)]
[InlineData (9, 9, false)]
[InlineData (10, 10, false)]
[InlineData (6, 7, false)]
[InlineData (1, 2, false)]
[InlineData (5, 6, false)]
public void Returns_Null_If_Not_Visible_And_SubView_Visible (int testX, int testY, bool expectedSubViewFound)
{
Application.Top = new ()
{
Width = 10, Height = 10,
Visible = false
};
var subview = new View
{
X = 1, Y = 2,
Width = 5, Height = 5
};
Application.Top.Add (subview);
subview.Visible = true;
Assert.True (subview.Visible);
Assert.False (Application.Top.Visible);
View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ();
Assert.Equal (expectedSubViewFound, found == subview);
Application.Top.Dispose ();
Application.ResetState (true);
}
// Test that GetViewsUnderMouse works if the start view has positive Adornments
[Theory]
[InlineData (0, 0, false)]
[InlineData (1, 1, false)]
[InlineData (9, 9, false)]
[InlineData (10, 10, false)]
[InlineData (7, 8, false)]
[InlineData (1, 2, false)]
[InlineData (2, 3, true)]
[InlineData (5, 6, true)]
[InlineData (6, 7, true)]
public void Returns_Correct_If_Start_Has_Adornments (int testX, int testY, bool expectedSubViewFound)
{
Application.Top = new ()
{
Width = 10, Height = 10
};
Application.Top.Margin.Thickness = new (1);
var subview = new View
{
X = 1, Y = 2,
Width = 5, Height = 5
};
Application.Top.Add (subview);
View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ();
Assert.Equal (expectedSubViewFound, found == subview);
Application.Top.Dispose ();
Application.ResetState (true);
}
// Test that GetViewsUnderMouse works if the start view has offset Viewport location
[Theory]
[InlineData (1, 0, 0, true)]
[InlineData (1, 1, 1, true)]
[InlineData (1, 2, 2, false)]
[InlineData (-1, 3, 3, true)]
[InlineData (-1, 2, 2, true)]
[InlineData (-1, 1, 1, false)]
[InlineData (-1, 0, 0, false)]
public void Returns_Correct_If_Start_Has_Offset_Viewport (int offset, int testX, int testY, bool expectedSubViewFound)
{
Application.Top = new ()
{
Width = 10, Height = 10,
ViewportSettings = ViewportSettings.AllowNegativeLocation
};
Application.Top.Viewport = new (offset, offset, 10, 10);
var subview = new View
{
X = 1, Y = 1,
Width = 2, Height = 2
};
Application.Top.Add (subview);
View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ();
Assert.Equal (expectedSubViewFound, found == subview);
Application.Top.Dispose ();
Application.ResetState (true);
}
[Theory]
[InlineData (9, 9, true)]
[InlineData (0, 0, false)]
[InlineData (1, 1, false)]
[InlineData (10, 10, false)]
[InlineData (7, 8, false)]
[InlineData (1, 2, false)]
[InlineData (2, 3, false)]
[InlineData (5, 6, false)]
[InlineData (6, 7, false)]
public void Returns_Correct_If_Start_Has_Adornment_WithSubview (int testX, int testY, bool expectedSubViewFound)
{
Application.Top = new ()
{
Width = 10, Height = 10
};
Application.Top.Padding.Thickness = new (1);
var subview = new View
{
X = Pos.AnchorEnd (1), Y = Pos.AnchorEnd (1),
Width = 1, Height = 1
};
Application.Top.Padding.Add (subview);
Application.Top.BeginInit ();
Application.Top.EndInit ();
View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ();
Assert.Equal (expectedSubViewFound, found == subview);
Application.Top.Dispose ();
Application.ResetState (true);
}
[Theory]
[InlineData (0, 0, typeof (Margin))]
[InlineData (9, 9, typeof (Margin))]
[InlineData (1, 1, typeof (Border))]
[InlineData (8, 8, typeof (Border))]
[InlineData (2, 2, typeof (Padding))]
[InlineData (7, 7, typeof (Padding))]
[InlineData (5, 5, typeof (Toplevel))]
public void Returns_Adornment_If_Start_Has_Adornments (int testX, int testY, Type expectedAdornmentType)
{
Application.Top = new ()
{
Width = 10, Height = 10
};
Application.Top.Margin.Thickness = new (1);
Application.Top.Border.Thickness = new (1);
Application.Top.Padding.Thickness = new (1);
var subview = new View
{
X = 1, Y = 1,
Width = 1, Height = 1
};
Application.Top.Add (subview);
View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ();
Assert.Equal (expectedAdornmentType, found!.GetType ());
Application.Top.Dispose ();
Application.ResetState (true);
}
// Test that GetViewsUnderMouse works if the subview has positive Adornments
[Theory]
[InlineData (0, 0, false)]
[InlineData (1, 1, false)]
[InlineData (9, 9, false)]
[InlineData (10, 10, false)]
[InlineData (7, 8, false)]
[InlineData (6, 7, false)]
[InlineData (1, 2, false)]
[InlineData (5, 6, false)]
[InlineData (2, 3, true)]
public void Returns_Correct_If_SubView_Has_Adornments (int testX, int testY, bool expectedSubViewFound)
{
Application.Top = new ()
{
Width = 10, Height = 10
};
var subview = new View
{
X = 1, Y = 2,
Width = 5, Height = 5
};
subview.Margin.Thickness = new (1);
Application.Top.Add (subview);
View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ();
Assert.Equal (expectedSubViewFound, found == subview);
Application.Top.Dispose ();
Application.ResetState (true);
}
[Theory]
[InlineData (0, 0, false)]
[InlineData (1, 1, false)]
[InlineData (9, 9, false)]
[InlineData (10, 10, false)]
[InlineData (7, 8, false)]
[InlineData (6, 7, false)]
[InlineData (1, 2, false)]
[InlineData (5, 6, false)]
[InlineData (6, 5, false)]
[InlineData (5, 5, true)]
public void Returns_Correct_If_SubView_Has_Adornment_WithSubview (int testX, int testY, bool expectedSubViewFound)
{
Application.Top = new ()
{
Width = 10, Height = 10
};
// A subview with + Padding
var subview = new View
{
X = 1, Y = 1,
Width = 5, Height = 5
};
subview.Padding.Thickness = new (1);
// This subview will be at the bottom-right-corner of subview
// So screen-relative location will be X + Width - 1 = 5
var paddingSubview = new View
{
X = Pos.AnchorEnd (1),
Y = Pos.AnchorEnd (1),
Width = 1,
Height = 1
};
subview.Padding.Add (paddingSubview);
Application.Top.Add (subview);
Application.Top.BeginInit ();
Application.Top.EndInit ();
View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ();
Assert.Equal (expectedSubViewFound, found == paddingSubview);
Application.Top.Dispose ();
Application.ResetState (true);
}
[Theory]
[InlineData (0, 0, false)]
[InlineData (1, 1, false)]
[InlineData (9, 9, false)]
[InlineData (10, 10, false)]
[InlineData (7, 8, false)]
[InlineData (6, 7, false)]
[InlineData (1, 2, false)]
[InlineData (5, 6, false)]
[InlineData (6, 5, false)]
[InlineData (5, 5, true)]
public void Returns_Correct_If_SubView_Is_Scrolled_And_Has_Adornment_WithSubview (int testX, int testY, bool expectedSubViewFound)
{
Application.Top = new ()
{
Width = 10, Height = 10
};
// A subview with + Padding
var subview = new View
{
X = 1, Y = 1,
Width = 5, Height = 5
};
subview.Padding.Thickness = new (1);
// Scroll the subview
subview.SetContentSize (new (10, 10));
subview.Viewport = subview.Viewport with { Location = new (1, 1) };
// This subview will be at the bottom-right-corner of subview
// So screen-relative location will be X + Width - 1 = 5
var paddingSubview = new View
{
X = Pos.AnchorEnd (1),
Y = Pos.AnchorEnd (1),
Width = 1,
Height = 1
};
subview.Padding.Add (paddingSubview);
Application.Top.Add (subview);
Application.Top.BeginInit ();
Application.Top.EndInit ();
View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ();
Assert.Equal (expectedSubViewFound, found == paddingSubview);
Application.Top.Dispose ();
Application.ResetState (true);
}
// Test that GetViewsUnderMouse works with nested subviews
[Theory]
[InlineData (0, 0, -1)]
[InlineData (9, 9, -1)]
[InlineData (10, 10, -1)]
[InlineData (1, 1, 0)]
[InlineData (1, 2, 0)]
[InlineData (2, 2, 1)]
[InlineData (3, 3, 2)]
[InlineData (5, 5, 2)]
public void Returns_Correct_With_NestedSubViews (int testX, int testY, int expectedSubViewFound)
{
Application.Top = new ()
{
Width = 10, Height = 10
};
var numSubViews = 3;
List<View> subviews = new ();
for (var i = 0; i < numSubViews; i++)
{
var subview = new View
{
X = 1, Y = 1,
Width = 5, Height = 5
};
subviews.Add (subview);
if (i > 0)
{
subviews [i - 1].Add (subview);
}
}
Application.Top.Add (subviews [0]);
View? found = View.GetViewsUnderMouse (new (testX, testY)).LastOrDefault ();
Assert.Equal (expectedSubViewFound, subviews.IndexOf (found!));
Application.Top.Dispose ();
Application.ResetState (true);
}
[Theory]
[InlineData (0, 0, new [] { "top" })]
[InlineData (9, 9, new [] { "top" })]
[InlineData (10, 10, new string [] { })]
[InlineData (1, 1, new [] { "top", "view" })]
[InlineData (1, 2, new [] { "top", "view" })]
[InlineData (2, 1, new [] { "top", "view" })]
[InlineData (2, 2, new [] { "top", "view", "subView" })]
[InlineData (3, 3, new [] { "top" })] // clipped
[InlineData (2, 3, new [] { "top" })] // clipped
public void GetViewsUnderMouse_Tiled_Subviews (int mouseX, int mouseY, string [] viewIdStrings)
{
// Arrange
Application.Top = new ()
{
Frame = new (0, 0, 10, 10),
Id = "top"
};
var view = new View
{
Id = "view",
X = 1,
Y = 1,
Width = 2,
Height = 2,
Arrangement = ViewArrangement.Overlapped
}; // at 1,1 to 3,2 (screen)
var subView = new View
{
Id = "subView",
X = 1,
Y = 1,
Width = 2,
Height = 2,
Arrangement = ViewArrangement.Overlapped
}; // at 2,2 to 4,3 (screen)
view.Add (subView);
Application.Top.Add (view);
List<View?> found = View.GetViewsUnderMouse (new (mouseX, mouseY));
string [] foundIds = found.Select (v => v!.Id).ToArray ();
Assert.Equal (viewIdStrings, foundIds);
Application.Top.Dispose ();
Application.ResetState (true);
}
}

View File

@@ -0,0 +1,349 @@
using System.ComponentModel;
namespace Terminal.Gui.ViewMouseTests;
[Trait ("Category", "Input")]
public class MouseEnterLeaveTests
{
private class TestView : View
{
public TestView ()
{
MouseEnter += OnMouseEnterHandler;
MouseLeave += OnMouseLeaveHandler;
}
public bool CancelOnEnter { get; init; }
public bool CancelEnterEvent { get; init; }
public bool OnMouseEnterCalled { get; private set; }
public bool OnMouseLeaveCalled { get; private set; }
protected override bool OnMouseEnter (CancelEventArgs eventArgs)
{
OnMouseEnterCalled = true;
eventArgs.Cancel = CancelOnEnter;
base.OnMouseEnter (eventArgs);
return eventArgs.Cancel;
}
protected override void OnMouseLeave ()
{
OnMouseLeaveCalled = true;
base.OnMouseLeave ();
}
public bool MouseEnterRaised { get; private set; }
public bool MouseLeaveRaised { get; private set; }
private void OnMouseEnterHandler (object s, CancelEventArgs e)
{
MouseEnterRaised = true;
if (CancelEnterEvent)
{
e.Cancel = true;
}
}
private void OnMouseLeaveHandler (object s, EventArgs e) { MouseLeaveRaised = true; }
}
[Fact]
public void NewMouseEnterEvent_ViewIsEnabledAndVisible_CallsOnMouseEnter ()
{
// Arrange
var view = new TestView
{
Enabled = true,
Visible = true
};
var mouseEvent = new MouseEventArgs ();
var eventArgs = new CancelEventArgs ();
// Act
bool? cancelled = view.NewMouseEnterEvent (eventArgs);
// Assert
Assert.True (view.OnMouseEnterCalled);
Assert.False (cancelled);
Assert.False (eventArgs.Cancel);
// Cleanup
view.Dispose ();
}
[Fact]
public void NewMouseEnterEvent_ViewIsDisabled_CallsOnMouseEnter ()
{
// Arrange
var view = new TestView
{
Enabled = false,
Visible = true
};
var eventArgs = new CancelEventArgs ();
// Act
bool? cancelled = view.NewMouseEnterEvent (eventArgs);
// Assert
Assert.True (view.OnMouseEnterCalled);
Assert.False (cancelled);
Assert.False (eventArgs.Cancel);
// Cleanup
view.Dispose ();
}
[Fact]
public void NewMouseEnterEvent_ViewIsNotVisible_DoesNotCallOnMouseEnter ()
{
// Arrange
var view = new TestView
{
Enabled = true,
Visible = false
};
var eventArgs = new CancelEventArgs ();
// Act
bool? cancelled = view.NewMouseEnterEvent (eventArgs);
// Assert
Assert.False (view.OnMouseEnterCalled);
Assert.Null (cancelled);
Assert.False (eventArgs.Cancel);
// Cleanup
view.Dispose ();
}
[Fact]
public void NewMouseLeaveEvent_ViewIsVisible_CallsOnMouseLeave ()
{
// Arrange
var view = new TestView
{
Enabled = true, Visible = true
};
var mouseEvent = new MouseEventArgs ();
// Act
view.NewMouseLeaveEvent ();
// Assert
Assert.True (view.OnMouseLeaveCalled);
Assert.False (mouseEvent.Handled);
// Cleanup
view.Dispose ();
}
[Fact]
public void NewMouseLeaveEvent_ViewIsNotVisible_CallsOnMouseLeave ()
{
// Arrange
var view = new TestView
{
Enabled = true,
Visible = false
};
var mouseEvent = new MouseEventArgs ();
// Act
view.NewMouseLeaveEvent ();
// Assert
Assert.True (view.OnMouseLeaveCalled);
Assert.False (mouseEvent.Handled);
// Cleanup
view.Dispose ();
}
// Events
[Fact]
public void NewMouseEnterEvent_ViewIsEnabledAndVisible_RaisesMouseEnter ()
{
// Arrange
var view = new TestView
{
Enabled = true,
Visible = true
};
var eventArgs = new CancelEventArgs ();
// Act
bool? cancelled = view.NewMouseEnterEvent (eventArgs);
// Assert
Assert.True (view.MouseEnterRaised);
Assert.False (cancelled);
Assert.False (eventArgs.Cancel);
// Cleanup
view.Dispose ();
}
[Fact]
public void NewMouseEnterEvent_ViewIsDisabled_RaisesMouseEnter ()
{
// Arrange
var view = new TestView
{
Enabled = false,
Visible = true
};
var eventArgs = new CancelEventArgs ();
// Act
bool? cancelled = view.NewMouseEnterEvent (eventArgs);
// Assert
Assert.True (view.MouseEnterRaised);
Assert.False (cancelled);
Assert.False (eventArgs.Cancel);
// Cleanup
view.Dispose ();
}
[Fact]
public void NewMouseEnterEvent_ViewIsNotVisible_DoesNotRaiseMouseEnter ()
{
// Arrange
var view = new TestView
{
Enabled = true,
Visible = false
};
var eventArgs = new CancelEventArgs ();
// Act
bool? cancelled = view.NewMouseEnterEvent (eventArgs);
// Assert
Assert.False (view.MouseEnterRaised);
Assert.Null (cancelled);
Assert.False (eventArgs.Cancel);
// Cleanup
view.Dispose ();
}
[Fact]
public void NewMouseLeaveEvent_ViewIsVisible_RaisesMouseLeave ()
{
// Arrange
var view = new TestView
{
Enabled = true,
Visible = true
};
var mouseEvent = new MouseEventArgs ();
// Act
view.NewMouseLeaveEvent ();
// Assert
Assert.True (view.MouseLeaveRaised);
Assert.False (mouseEvent.Handled);
// Cleanup
view.Dispose ();
}
[Fact]
public void NewMouseLeaveEvent_ViewIsNotVisible_RaisesMouseLeave ()
{
// Arrange
var view = new TestView
{
Enabled = true,
Visible = false
};
var mouseEvent = new MouseEventArgs ();
// Act
view.NewMouseLeaveEvent ();
// Assert
Assert.True (view.MouseLeaveRaised);
Assert.False (mouseEvent.Handled);
// Cleanup
view.Dispose ();
}
// Handled tests
[Fact]
public void NewMouseEnterEvent_HandleOnMouseEnter_Event_Not_Raised ()
{
// Arrange
var view = new TestView
{
Enabled = true,
Visible = true,
CancelOnEnter = true
};
var eventArgs = new CancelEventArgs ();
// Act
bool? cancelled = view.NewMouseEnterEvent (eventArgs);
// Assert
Assert.True (view.OnMouseEnterCalled);
Assert.True (cancelled);
Assert.True (eventArgs.Cancel);
Assert.False (view.MouseEnterRaised);
// Cleanup
view.Dispose ();
}
[Fact]
public void NewMouseEnterEvent_HandleMouseEnterEvent ()
{
// Arrange
var view = new TestView
{
Enabled = true,
Visible = true,
CancelEnterEvent = true
};
var eventArgs = new CancelEventArgs ();
// Act
bool? cancelled = view.NewMouseEnterEvent (eventArgs);
// Assert
Assert.True (view.OnMouseEnterCalled);
Assert.True (cancelled);
Assert.True (eventArgs.Cancel);
Assert.True (view.MouseEnterRaised);
// Cleanup
view.Dispose ();
}
}

View File

@@ -0,0 +1,545 @@
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewMouseTests;
[Trait ("Category", "Input")]
public class MouseTests (ITestOutputHelper output) : TestsAllViews
{
[Theory]
[InlineData (false, false, false)]
[InlineData (true, false, true)]
[InlineData (true, true, true)]
public void MouseClick_SetsFocus_If_CanFocus (bool canFocus, bool setFocus, bool expectedHasFocus)
{
var superView = new View { CanFocus = true, Height = 1, Width = 15 };
var focusedView = new View { CanFocus = true, Width = 1, Height = 1 };
var testView = new View { CanFocus = canFocus, X = 4, Width = 4, Height = 1 };
superView.Add (focusedView, testView);
focusedView.SetFocus ();
Assert.True (superView.HasFocus);
Assert.True (focusedView.HasFocus);
Assert.False (testView.HasFocus);
if (setFocus)
{
testView.SetFocus ();
}
testView.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked });
Assert.True (superView.HasFocus);
Assert.Equal (expectedHasFocus, testView.HasFocus);
}
[Theory]
[InlineData (false, false, 1)]
[InlineData (true, false, 1)]
[InlineData (true, true, 1)]
public void MouseClick_Raises_Selecting (bool canFocus, bool setFocus, int expectedSelectingCount)
{
var superView = new View { CanFocus = true, Height = 1, Width = 15 };
var focusedView = new View { CanFocus = true, Width = 1, Height = 1 };
var testView = new View { CanFocus = canFocus, X = 4, Width = 4, Height = 1 };
superView.Add (focusedView, testView);
focusedView.SetFocus ();
Assert.True (superView.HasFocus);
Assert.True (focusedView.HasFocus);
Assert.False (testView.HasFocus);
if (setFocus)
{
testView.SetFocus ();
}
int selectingCount = 0;
testView.Selecting += (sender, args) => selectingCount++;
testView.NewMouseEvent (new () { Position = new (0, 0), Flags = MouseFlags.Button1Clicked });
Assert.True (superView.HasFocus);
Assert.Equal (expectedSelectingCount, selectingCount);
}
// TODO: Add more tests that ensure the above test works with positive adornments
// Test drag to move
[Theory]
[InlineData (0, 0, 0, 0, false)]
[InlineData (0, 0, 0, 4, false)]
[InlineData (1, 0, 0, 4, false)]
[InlineData (0, 1, 0, 4, true)]
[InlineData (0, 0, 1, 4, false)]
[InlineData (1, 1, 0, 3, false)]
[InlineData (1, 1, 0, 4, false)]
[InlineData (1, 1, 0, 5, true)]
[InlineData (1, 1, 0, 6, false)]
[InlineData (1, 1, 0, 11, false)]
[InlineData (1, 1, 0, 12, true)]
[InlineData (1, 1, 0, 13, false)]
[InlineData (1, 1, 0, 14, false)]
[AutoInitShutdown]
public void ButtonPressed_In_Border_Starts_Drag (int marginThickness, int borderThickness, int paddingThickness, int xy, bool expectedMoved)
{
var testView = new View
{
CanFocus = true,
X = 4,
Y = 4,
Width = 10,
Height = 10,
Arrangement = ViewArrangement.Movable
};
testView.Margin.Thickness = new (marginThickness);
testView.Border.Thickness = new (borderThickness);
testView.Padding.Thickness = new (paddingThickness);
var top = new Toplevel ();
top.Add (testView);
var rs = Application.Begin (top);
Assert.Equal (4, testView.Frame.X);
Assert.Equal (new Point (4, 4), testView.Frame.Location);
Application.RaiseMouseEvent (new () { ScreenPosition = new (xy, xy), Flags = MouseFlags.Button1Pressed });
Application.RaiseMouseEvent (new () { ScreenPosition = new (xy + 1, xy + 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition });
Application.RunIteration(ref rs, false);
Assert.Equal (expectedMoved, new Point (5, 5) == testView.Frame.Location);
top.Dispose ();
}
[Theory]
[InlineData (MouseFlags.WheeledUp | MouseFlags.ButtonCtrl, MouseFlags.WheeledLeft)]
[InlineData (MouseFlags.WheeledDown | MouseFlags.ButtonCtrl, MouseFlags.WheeledRight)]
public void WheeledLeft_WheeledRight (MouseFlags mouseFlags, MouseFlags expectedMouseFlagsFromEvent)
{
MouseFlags mouseFlagsFromEvent = MouseFlags.None;
var view = new View ();
view.MouseEvent += (s, e) => mouseFlagsFromEvent = e.Flags;
view.NewMouseEvent (new MouseEventArgs () { Flags = mouseFlags });
Assert.Equal (mouseFlagsFromEvent, expectedMouseFlagsFromEvent);
}
[Fact]
public void NewMouseEvent_Invokes_MouseEvent_Properly ()
{
View view = new ()
{
Width = 1,
Height = 1,
};
bool mouseEventInvoked = false;
view.MouseEvent += (s, e) =>
{
mouseEventInvoked = true;
e.Handled = true;
};
MouseEventArgs me = new ();
view.NewMouseEvent (me);
Assert.True (mouseEventInvoked);
Assert.True (me.Handled);
view.Dispose ();
}
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_NewMouseEvent_Enabled_False_Does_Not_Set_Handled (Type viewType)
{
var view = CreateInstanceIfNotGeneric (viewType);
if (view == null)
{
output.WriteLine ($"Ignoring {viewType} - It's a Generic");
return;
}
view.Enabled = false;
var me = new MouseEventArgs ();
view.NewMouseEvent (me);
Assert.False (me.Handled);
view.Dispose ();
}
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_NewMouseEvent_Clicked_Enabled_False_Does_Not_Set_Handled (Type viewType)
{
var view = CreateInstanceIfNotGeneric (viewType);
if (view == null)
{
output.WriteLine ($"Ignoring {viewType} - It's a Generic");
return;
}
view.Enabled = false;
var me = new MouseEventArgs ()
{
Flags = MouseFlags.Button1Clicked
};
view.NewMouseEvent (me);
Assert.False (me.Handled);
view.Dispose ();
}
[Theory]
[InlineData (MouseFlags.Button1Pressed, MouseFlags.Button1Released, MouseFlags.Button1Clicked)]
[InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released, MouseFlags.Button2Clicked)]
[InlineData (MouseFlags.Button3Pressed, MouseFlags.Button3Released, MouseFlags.Button3Clicked)]
[InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)]
public void WantContinuousButtonPressed_False_Button_Press_Release_DoesNotClick (MouseFlags pressed, MouseFlags released, MouseFlags clicked)
{
var me = new MouseEventArgs ();
var view = new View ()
{
Width = 1,
Height = 1,
WantContinuousButtonPressed = false
};
var clickedCount = 0;
view.MouseClick += (s, e) => clickedCount++;
me.Flags = pressed;
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
me.Flags = pressed;
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
me.Flags = released;
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
me.Flags = clicked;
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
view.Dispose ();
}
[Theory]
[InlineData (MouseFlags.Button1Clicked)]
[InlineData (MouseFlags.Button2Clicked)]
[InlineData (MouseFlags.Button3Clicked)]
[InlineData (MouseFlags.Button4Clicked)]
public void WantContinuousButtonPressed_True_Button_Clicked_Raises_MouseClick (MouseFlags clicked)
{
var me = new MouseEventArgs ();
var view = new View ()
{
Width = 1,
Height = 1,
WantContinuousButtonPressed = true
};
var clickedCount = 0;
view.MouseClick += (s, e) => clickedCount++;
me.Flags = clicked;
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
view.Dispose ();
}
[Theory]
[InlineData (MouseFlags.Button1Clicked)]
[InlineData (MouseFlags.Button2Clicked)]
[InlineData (MouseFlags.Button3Clicked)]
[InlineData (MouseFlags.Button4Clicked)]
public void WantContinuousButtonPressed_True_Button_Clicked_Raises_Selecting (MouseFlags clicked)
{
var me = new MouseEventArgs ();
var view = new View ()
{
Width = 1,
Height = 1,
WantContinuousButtonPressed = true
};
var selectingCount = 0;
view.Selecting += (s, e) => selectingCount++;
me.Flags = clicked;
view.NewMouseEvent (me);
Assert.Equal (1, selectingCount);
view.Dispose ();
}
[Theory]
[InlineData (MouseFlags.Button1Pressed, MouseFlags.Button1Released)]
[InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released)]
[InlineData (MouseFlags.Button3Pressed, MouseFlags.Button3Released)]
[InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released)]
public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Button_Press_Release_Clicks (MouseFlags pressed, MouseFlags released)
{
var me = new MouseEventArgs ();
var view = new View ()
{
Width = 1,
Height = 1,
WantContinuousButtonPressed = true,
WantMousePositionReports = true
};
var clickedCount = 0;
view.MouseClick += (s, e) => clickedCount++;
me.Flags = pressed;
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
me.Flags = pressed;
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
me.Handled = false;
me.Flags = released;
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
view.Dispose ();
}
[Theory]
[InlineData (MouseFlags.Button1Pressed, MouseFlags.Button1Released, MouseFlags.Button1Clicked)]
[InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released, MouseFlags.Button2Clicked)]
[InlineData (MouseFlags.Button3Pressed, MouseFlags.Button3Released, MouseFlags.Button3Clicked)]
[InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)]
public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Button_Press_Release_Clicks_Repeatedly (MouseFlags pressed, MouseFlags released, MouseFlags clicked)
{
var me = new MouseEventArgs ();
var view = new View ()
{
Width = 1,
Height = 1,
WantContinuousButtonPressed = true,
WantMousePositionReports = true
};
var clickedCount = 0;
view.MouseClick += (s, e) => clickedCount++;
me.Flags = pressed;
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
me.Flags = pressed;
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
me.Handled = false;
me.Flags = released;
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
me.Handled = false;
me.Flags = clicked;
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
view.Dispose ();
Application.ResetState (ignoreDisposed: true);
}
[Fact]
public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Move_InViewport_OutOfViewport_Keeps_Counting ()
{
var me = new MouseEventArgs ();
var view = new View ()
{
Width = 1,
Height = 1,
WantContinuousButtonPressed = true,
WantMousePositionReports = true
};
var clickedCount = 0;
view.MouseClick += (s, e) => clickedCount++;
// Start in Viewport
me.Flags = MouseFlags.Button1Pressed;
me.Position = me.Position with { X = 0 };
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
// Move out of Viewport
me.Flags = MouseFlags.Button1Pressed;
me.Position = me.Position with { X = 1 };
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
me.Handled = false;
// Move into Viewport
me.Flags = MouseFlags.Button1Pressed;
me.Position = me.Position with { X = 0 };
view.NewMouseEvent (me);
Assert.Equal (2, clickedCount);
me.Handled = false;
view.Dispose ();
Application.ResetState (ignoreDisposed: true);
}
[Theory]
[InlineData (HighlightStyle.None, 0, 0)]
[InlineData (HighlightStyle.Pressed | HighlightStyle.PressedOutside, 1, 1)]
public void HighlightOnPress_Fires_Events_And_Highlights (HighlightStyle highlightOnPress, int expectedEnabling, int expectedDisabling)
{
var view = new View ()
{
CanFocus = true,
HighlightStyle = highlightOnPress,
Height = 1,
Width = 1
};
int enablingHighlight = 0;
int disablingHighlight = 0;
view.Highlight += View_Highlight;
view.ColorScheme = new ColorScheme (new Attribute (ColorName16.Red, ColorName16.Blue));
ColorScheme originalColorScheme = view.ColorScheme;
view.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, });
if (highlightOnPress != HighlightStyle.None)
{
Assert.NotEqual (originalColorScheme, view.ColorScheme);
}
else
{
Assert.Equal (originalColorScheme, view.ColorScheme);
}
view.NewMouseEvent (new () { Flags = MouseFlags.Button1Released, });
Assert.Equal (originalColorScheme, view.ColorScheme);
Assert.Equal (expectedEnabling, enablingHighlight);
Assert.Equal (expectedDisabling, disablingHighlight);
view.Dispose ();
return;
void View_Highlight (object sender, CancelEventArgs<HighlightStyle> e)
{
if (e.NewValue == HighlightStyle.None)
{
disablingHighlight++;
}
else
{
enablingHighlight++;
}
}
}
// TODO: Add tests for each combination of HighlightFlags
[Theory]
[InlineData (0)]
[InlineData (1)]
[InlineData (10)]
public void HighlightOnPress_Move_Keeps_Highlight (int x)
{
var view = new View ()
{
CanFocus = true,
HighlightStyle = HighlightStyle.Pressed | HighlightStyle.PressedOutside,
Height = 1,
Width = 1
};
int enablingHighlight = 0;
int disablingHighlight = 0;
view.Highlight += View_Highlight;
bool inViewport = view.Viewport.Contains (x, 0);
// Start at 0,0 ; in viewport
view.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
Assert.Equal (1, enablingHighlight);
Assert.Equal (0, disablingHighlight);
// Move to x,0
view.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
if (inViewport)
{
Assert.Equal (2, enablingHighlight);
Assert.Equal (0, disablingHighlight);
}
else
{
Assert.Equal (2, enablingHighlight);
Assert.Equal (0, disablingHighlight);
}
// Move backto 0,0 ; in viewport
view.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
if (inViewport)
{
Assert.Equal (3, enablingHighlight);
Assert.Equal (0, disablingHighlight);
}
else
{
Assert.Equal (3, enablingHighlight);
Assert.Equal (0, disablingHighlight);
}
view.Dispose ();
return;
void View_Highlight (object sender, CancelEventArgs<HighlightStyle> e)
{
if (e.NewValue == HighlightStyle.None)
{
disablingHighlight++;
}
else
{
enablingHighlight++;
}
}
}
}

View File

@@ -0,0 +1,242 @@
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class AddRemoveNavigationTests () : TestsAllViews
{
[Fact]
public void Add_First_Subview_Gets_Focus ()
{
View top = new View ()
{
Id = "top",
CanFocus = true
};
top.SetFocus ();
Assert.True (top.HasFocus);
View subView = new View ()
{
Id = "subView",
CanFocus = true
};
top.Add (subView);
Assert.True (top.HasFocus);
Assert.Equal (subView, top.Focused);
Assert.True (subView.HasFocus);
}
[Fact]
public void Add_Subsequent_Subview_Gets_Focus ()
{
View top = new View ()
{
Id = "top",
CanFocus = true
};
top.SetFocus ();
Assert.True (top.HasFocus);
View subView = new View ()
{
Id = "subView",
CanFocus = true
};
top.Add (subView);
Assert.True (subView.HasFocus);
View subView2 = new View ()
{
Id = "subView2",
CanFocus = true
};
top.Add (subView2);
Assert.True (subView2.HasFocus);
}
[Fact]
public void Add_Nested_Subviews_Deepest_Gets_Focus ()
{
View top = new View ()
{
Id = "top",
CanFocus = true
};
top.SetFocus ();
Assert.True (top.HasFocus);
View subView = new View ()
{
Id = "subView",
CanFocus = true
};
View subSubView = new View ()
{
Id = "subSubView",
CanFocus = true
};
subView.Add (subSubView);
top.Add (subView);
Assert.True (top.HasFocus);
Assert.Equal (subView, top.Focused);
Assert.True (subView.HasFocus);
Assert.True (subSubView.HasFocus);
}
[Fact]
public void Remove_Subview_Raises_HasFocusChanged ()
{
var top = new View
{
Id = "top",
CanFocus = true
};
var subView1 = new View
{
Id = "subView1",
CanFocus = true
};
var subView2 = new View
{
Id = "subView2",
CanFocus = true
};
top.Add (subView1, subView2);
var subView1HasFocusChangedTrueCount = 0;
var subView1HasFocusChangedFalseCount = 0;
subView1.HasFocusChanged += (s, e) =>
{
if (e.NewValue)
{
subView1HasFocusChangedTrueCount++;
}
else
{
subView1HasFocusChangedFalseCount++;
}
};
var subView2HasFocusChangedTrueCount = 0;
var subView2HasFocusChangedFalseCount = 0;
subView2.HasFocusChanged += (s, e) =>
{
if (e.NewValue)
{
subView2HasFocusChangedTrueCount++;
}
else
{
subView2HasFocusChangedFalseCount++;
}
};
top.SetFocus ();
Assert.True (top.HasFocus);
Assert.True (subView1.HasFocus);
Assert.False (subView2.HasFocus);
Assert.Equal (1, subView1HasFocusChangedTrueCount);
Assert.Equal (0, subView1HasFocusChangedFalseCount);
Assert.Equal (0, subView2HasFocusChangedTrueCount);
Assert.Equal (0, subView2HasFocusChangedFalseCount);
top.Remove (subView1); // this should have the same resuilt as top.AdvanceFocus (NavigationDirection.Forward, null);
Assert.False (subView1.HasFocus);
Assert.True (subView2.HasFocus);
Assert.Equal (1, subView1HasFocusChangedTrueCount);
Assert.Equal (1, subView1HasFocusChangedFalseCount);
Assert.Equal (1, subView2HasFocusChangedTrueCount);
Assert.Equal (0, subView2HasFocusChangedFalseCount);
}
[Fact]
public void Remove_Focused_Subview_Keeps_Focus_And_SubView_Looses_Focus ()
{
View top = new View ()
{
Id = "top",
CanFocus = true
};
View subView = new View ()
{
Id = "subView",
CanFocus = true
};
top.Add (subView);
top.SetFocus ();
Assert.True (top.HasFocus);
Assert.Equal (subView, top.Focused);
Assert.True (subView.HasFocus);
top.Remove (subView);
Assert.True (top.HasFocus);
Assert.Null (top.Focused);
Assert.False (subView.HasFocus);
}
[Fact]
public void Remove_Focused_Subview_Keeps_Focus_And_SubView_Looses_Focus_And_Next_Gets_Focus ()
{
View top = new View ()
{
Id = "top",
CanFocus = true
};
View subView1 = new View ()
{
Id = "subView1",
CanFocus = true
};
View subView2 = new View ()
{
Id = "subView2",
CanFocus = true
};
top.Add (subView1, subView2);
top.SetFocus ();
Assert.True (top.HasFocus);
Assert.Equal (subView1, top.Focused);
Assert.True (subView1.HasFocus);
Assert.False (subView2.HasFocus);
top.Remove (subView1);
Assert.True (top.HasFocus);
Assert.True (subView2.HasFocus);
Assert.Equal (subView2, top.Focused);
Assert.False (subView1.HasFocus);
}
}

View File

@@ -0,0 +1,647 @@
using System.Runtime.Intrinsics;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class AdvanceFocusTests ()
{
[Fact]
public void AdvanceFocus_CanFocus_Mixed ()
{
var r = new View ();
var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop };
var v2 = new View { CanFocus = false, TabStop = TabBehavior.TabStop };
var v3 = new View { CanFocus = false, TabStop = TabBehavior.NoStop };
r.Add (v1, v2, v3);
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.Dispose ();
}
[Theory]
[CombinatorialData]
public void AdvanceFocus_Change_CanFocus_Works ([CombinatorialValues (TabBehavior.NoStop, TabBehavior.TabStop, TabBehavior.TabGroup)] TabBehavior behavior)
{
var r = new View { CanFocus = true };
var v1 = new View ();
var v2 = new View ();
var v3 = new View ();
Assert.True (r.CanFocus);
Assert.False (v1.CanFocus);
Assert.False (v2.CanFocus);
Assert.False (v3.CanFocus);
r.Add (v1, v2, v3);
r.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
v1.CanFocus = true;
v1.TabStop = behavior;
r.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
v2.CanFocus = true;
v2.TabStop = behavior;
r.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.False (v1.HasFocus);
Assert.True (v2.HasFocus);
Assert.False (v3.HasFocus);
v3.CanFocus = true;
v3.TabStop = behavior;
r.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.True (v3.HasFocus);
r.Dispose ();
}
[Fact]
public void AdvanceFocus_Subviews_TabStop ()
{
TabBehavior behavior = TabBehavior.TabStop;
var top = new View { Id = "top", CanFocus = true };
var v1 = new View { Id = "v1", CanFocus = true, TabStop = behavior };
var v2 = new View { Id = "v2", CanFocus = true, TabStop = behavior };
var v3 = new View { Id = "v3", CanFocus = false, TabStop = behavior };
top.Add (v1, v2, v3);
// Cycle through v1 & v2
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (v1.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (v2.HasFocus);
// Should cycle back to v1
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (v1.HasFocus);
// Go backwards
top.AdvanceFocus (NavigationDirection.Backward, behavior);
Assert.True (v2.HasFocus);
top.AdvanceFocus (NavigationDirection.Backward, behavior);
Assert.True (v1.HasFocus);
top.Dispose ();
}
[Fact]
public void AdvanceFocus_Compound_Subview_TabStop ()
{
TabBehavior behavior = TabBehavior.TabStop;
var top = new View { Id = "top", CanFocus = true };
var compoundSubview = new View
{
CanFocus = true,
Id = "compoundSubview",
TabStop = behavior
};
var v1 = new View { Id = "v1", CanFocus = true, TabStop = behavior };
var v2 = new View { Id = "v2", CanFocus = true, TabStop = behavior };
var v3 = new View { Id = "v3", CanFocus = false, TabStop = behavior };
compoundSubview.Add (v1, v2, v3);
top.Add (compoundSubview);
// Cycle through v1 & v2
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.False (v1.HasFocus);
Assert.True (v2.HasFocus);
Assert.False (v3.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
// Add another subview
View otherSubview = new ()
{
CanFocus = true,
TabStop = behavior,
Id = "otherSubview"
};
top.Add (otherSubview);
// Adding a focusable subview causes advancefocus
Assert.True (otherSubview.HasFocus);
Assert.False (v1.HasFocus);
// Cycle through v1 & v2
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.False (v1.HasFocus);
Assert.True (v2.HasFocus);
Assert.False (v3.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
Assert.True (otherSubview.HasFocus);
// v2 was previously focused down the compoundSubView focus chain
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.False (v1.HasFocus);
Assert.True (v2.HasFocus);
Assert.False (v3.HasFocus);
top.Dispose ();
}
[Fact]
public void AdvanceFocus_CompoundCompound_Subview_TabStop ()
{
TabBehavior behavior = TabBehavior.TabStop;
var top = new View { Id = "top", CanFocus = true };
var topv1 = new View { Id = "topv1", CanFocus = true, TabStop = behavior };
var topv2 = new View { Id = "topv2", CanFocus = true, TabStop = behavior };
var topv3 = new View { Id = "topv3", CanFocus = false, TabStop = behavior };
top.Add (topv1, topv2, topv3);
var compoundSubview = new View
{
CanFocus = true,
Id = "compoundSubview",
TabStop = behavior
};
var v1 = new View { Id = "v1", CanFocus = true, TabStop = behavior };
var v2 = new View { Id = "v2", CanFocus = true, TabStop = behavior };
var v3 = new View { Id = "v3", CanFocus = false, TabStop = behavior };
compoundSubview.Add (v1, v2, v3);
var compoundCompoundSubview = new View
{
CanFocus = true,
Id = "compoundCompoundSubview",
TabStop = behavior
};
var v4 = new View { Id = "v4", CanFocus = true, TabStop = behavior };
var v5 = new View { Id = "v5", CanFocus = true, TabStop = behavior };
var v6 = new View { Id = "v6", CanFocus = false, TabStop = behavior };
compoundCompoundSubview.Add (v4, v5, v6);
compoundSubview.Add (compoundCompoundSubview);
top.Add (compoundSubview);
top.SetFocus ();
Assert.True (topv1.HasFocus);
// Cycle through topv2
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (topv2.HasFocus);
// Cycle v1, v2, v4, v5
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (v1.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (v2.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (v4.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (v5.HasFocus);
// Should cycle back to topv1
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (topv1.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (topv2.HasFocus);
// Add another top subview. Should cycle to it after v5
View otherSubview = new ()
{
CanFocus = true,
TabStop = behavior,
Id = "otherSubview"
};
top.Add (otherSubview);
// Adding a focusable subview causes advancefocus
Assert.True (otherSubview.HasFocus);
// Cycle through topv1, topv2, v1, v2, v4, v5
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (topv1.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (topv2.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
// the above should have cycled to v5 since it was the previously most focused subview of compoundSubView
Assert.True (v5.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (otherSubview.HasFocus);
// Should cycle back to topv1
top.AdvanceFocus (NavigationDirection.Forward, behavior);
Assert.True (topv1.HasFocus);
top.Dispose ();
}
[Fact]
public void AdvanceFocus_Compound_Subview_TabGroup ()
{
var top = new View { Id = "top", CanFocus = true, TabStop = TabBehavior.TabGroup };
var compoundSubview = new View
{
CanFocus = true,
Id = "compoundSubview",
TabStop = TabBehavior.TabGroup
};
var tabStopView = new View { Id = "tabStop", CanFocus = true, TabStop = TabBehavior.TabStop };
var tabGroupView1 = new View { Id = "tabGroup1", CanFocus = true, TabStop = TabBehavior.TabGroup };
var tabGroupView2 = new View { Id = "tabGroup2", CanFocus = true, TabStop = TabBehavior.TabGroup };
compoundSubview.Add (tabStopView, tabGroupView1, tabGroupView2);
top.Add (compoundSubview);
top.SetFocus ();
Assert.True (tabStopView.HasFocus);
// TabGroup should cycle to tabGroup1 then tabGroup2
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
Assert.False (tabStopView.HasFocus);
Assert.True (tabGroupView1.HasFocus);
Assert.False (tabGroupView2.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
Assert.False (tabStopView.HasFocus);
Assert.False (tabGroupView1.HasFocus);
Assert.True (tabGroupView2.HasFocus);
// Add another TabGroup subview
View otherTabGroupSubview = new ()
{
CanFocus = true,
TabStop = TabBehavior.TabGroup,
Id = "otherTabGroupSubview"
};
top.Add (otherTabGroupSubview);
// Adding a focusable subview causes advancefocus
Assert.True (otherTabGroupSubview.HasFocus);
Assert.False (tabStopView.HasFocus);
// TagBroup navs to the other subview
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
Assert.Equal (compoundSubview, top.Focused);
Assert.True (tabStopView.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
Assert.Equal (compoundSubview, top.Focused);
Assert.True (tabGroupView1.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
Assert.Equal (compoundSubview, top.Focused);
Assert.True (tabGroupView2.HasFocus);
// Now go backwards
top.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup);
Assert.Equal (compoundSubview, top.Focused);
Assert.True (tabGroupView1.HasFocus);
top.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup);
Assert.Equal (otherTabGroupSubview, top.Focused);
Assert.True (otherTabGroupSubview.HasFocus);
top.Dispose ();
}
[Fact]
public void AdvanceFocus_NoStop_And_CanFocus_True_No_Focus ()
{
var r = new View ();
var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop };
var v2 = new View { CanFocus = true, TabStop = TabBehavior.NoStop };
var v3 = new View { CanFocus = true, TabStop = TabBehavior.NoStop };
r.Add (v1, v2, v3);
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.Dispose ();
}
[Fact]
public void AdvanceFocus_NoStop_Change_Enables_Stop ()
{
var r = new View { CanFocus = true };
var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop };
var v2 = new View { CanFocus = true, TabStop = TabBehavior.NoStop };
var v3 = new View { CanFocus = true, TabStop = TabBehavior.NoStop };
r.Add (v1, v2, v3);
v1.TabStop = TabBehavior.TabStop;
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.True (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
v2.TabStop = TabBehavior.TabStop;
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.True (v2.HasFocus);
Assert.False (v3.HasFocus);
v3.TabStop = TabBehavior.TabStop;
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.True (v3.HasFocus);
r.Dispose ();
}
[Fact]
public void AdvanceFocus_NoStop_Prevents_Stop ()
{
var r = new View ();
var v1 = new View { CanFocus = true, TabStop = TabBehavior.NoStop };
var v2 = new View { CanFocus = true, TabStop = TabBehavior.NoStop };
var v3 = new View { CanFocus = true, TabStop = TabBehavior.NoStop };
r.Add (v1, v2, v3);
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
}
[Fact]
public void AdvanceFocus_Null_And_CanFocus_False_No_Advance ()
{
var r = new View ();
var v1 = new View ();
var v2 = new View ();
var v3 = new View ();
Assert.False (v1.CanFocus);
Assert.Null (v1.TabStop);
r.Add (v1, v2, v3);
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
r.Dispose ();
}
[Fact]
public void AdvanceFocus_Subviews_Raises_HasFocusChanged ()
{
var top = new View
{
Id = "top",
CanFocus = true
};
var subView1 = new View
{
Id = "subView1",
CanFocus = true
};
var subView2 = new View
{
Id = "subView2",
CanFocus = true
};
top.Add (subView1, subView2);
var subView1HasFocusChangedTrueCount = 0;
var subView1HasFocusChangedFalseCount = 0;
subView1.HasFocusChanged += (s, e) =>
{
if (e.NewValue)
{
subView1HasFocusChangedTrueCount++;
}
else
{
subView1HasFocusChangedFalseCount++;
}
};
var subView2HasFocusChangedTrueCount = 0;
var subView2HasFocusChangedFalseCount = 0;
subView2.HasFocusChanged += (s, e) =>
{
if (e.NewValue)
{
subView2HasFocusChangedTrueCount++;
}
else
{
subView2HasFocusChangedFalseCount++;
}
};
top.SetFocus ();
Assert.True (top.HasFocus);
Assert.True (subView1.HasFocus);
Assert.False (subView2.HasFocus);
Assert.Equal (1, subView1HasFocusChangedTrueCount);
Assert.Equal (0, subView1HasFocusChangedFalseCount);
Assert.Equal (0, subView2HasFocusChangedTrueCount);
Assert.Equal (0, subView2HasFocusChangedFalseCount);
top.AdvanceFocus (NavigationDirection.Forward, null);
Assert.False (subView1.HasFocus);
Assert.True (subView2.HasFocus);
Assert.Equal (1, subView1HasFocusChangedTrueCount);
Assert.Equal (1, subView1HasFocusChangedFalseCount);
Assert.Equal (1, subView2HasFocusChangedTrueCount);
Assert.Equal (0, subView2HasFocusChangedFalseCount);
top.AdvanceFocus (NavigationDirection.Forward, null);
Assert.True (subView1.HasFocus);
Assert.False (subView2.HasFocus);
Assert.Equal (2, subView1HasFocusChangedTrueCount);
Assert.Equal (1, subView1HasFocusChangedFalseCount);
Assert.Equal (1, subView2HasFocusChangedTrueCount);
Assert.Equal (1, subView2HasFocusChangedFalseCount);
}
[Fact]
public void AdvanceFocus_Subviews_Raises_HasFocusChanging ()
{
var top = new View
{
Id = "top",
CanFocus = true
};
var subView1 = new View
{
Id = "subView1",
CanFocus = true
};
var subView2 = new View
{
Id = "subView2",
CanFocus = true
};
top.Add (subView1, subView2);
var subView1HasFocusChangingTrueCount = 0;
var subView1HasFocusChangingFalseCount = 0;
subView1.HasFocusChanging += (s, e) =>
{
if (e.NewValue)
{
subView1HasFocusChangingTrueCount++;
}
else
{
subView1HasFocusChangingFalseCount++;
}
};
var subView2HasFocusChangingTrueCount = 0;
var subView2HasFocusChangingFalseCount = 0;
subView2.HasFocusChanging += (s, e) =>
{
if (e.NewValue)
{
subView2HasFocusChangingTrueCount++;
}
else
{
subView2HasFocusChangingFalseCount++;
}
};
top.SetFocus ();
Assert.True (top.HasFocus);
Assert.True (subView1.HasFocus);
Assert.False (subView2.HasFocus);
Assert.Equal (1, subView1HasFocusChangingTrueCount);
Assert.Equal (0, subView1HasFocusChangingFalseCount);
Assert.Equal (0, subView2HasFocusChangingTrueCount);
Assert.Equal (0, subView2HasFocusChangingFalseCount);
top.AdvanceFocus (NavigationDirection.Forward, null);
Assert.False (subView1.HasFocus);
Assert.True (subView2.HasFocus);
Assert.Equal (1, subView1HasFocusChangingTrueCount);
Assert.Equal (1, subView1HasFocusChangingFalseCount);
Assert.Equal (1, subView2HasFocusChangingTrueCount);
Assert.Equal (0, subView2HasFocusChangingFalseCount);
top.AdvanceFocus (NavigationDirection.Forward, null);
Assert.True (subView1.HasFocus);
Assert.False (subView2.HasFocus);
Assert.Equal (2, subView1HasFocusChangingTrueCount);
Assert.Equal (1, subView1HasFocusChangingFalseCount);
Assert.Equal (1, subView2HasFocusChangingTrueCount);
Assert.Equal (1, subView2HasFocusChangingFalseCount);
}
[Fact]
public void AdvanceFocus_With_CanFocus_Are_All_True ()
{
var top = new View { Id = "top", CanFocus = true };
var v1 = new View { Id = "v1", CanFocus = true };
var v2 = new View { Id = "v2", CanFocus = true };
var v3 = new View { Id = "v3", CanFocus = true };
top.Add (v1, v2, v3);
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.True (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.False (v3.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.True (v2.HasFocus);
Assert.False (v3.HasFocus);
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
Assert.False (v1.HasFocus);
Assert.False (v2.HasFocus);
Assert.True (v3.HasFocus);
top.Dispose ();
}
[Theory]
[CombinatorialData]
public void TabStop_And_CanFocus_Are_Decoupled (bool canFocus, TabBehavior tabStop)
{
var view = new View { CanFocus = canFocus, TabStop = tabStop };
Assert.Equal (canFocus, view.CanFocus);
Assert.Equal (tabStop, view.TabStop);
}
}

View File

@@ -0,0 +1,365 @@
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class CanFocusTests () : TestsAllViews
{
[Fact]
public void CanFocus_False_Prevents_SubSubView_HasFocus ()
{
var view = new View { };
var subView = new View { };
var subSubView = new View { CanFocus = true };
subView.Add (subSubView);
view.Add (subView);
Assert.False (view.CanFocus);
Assert.False (subView.CanFocus);
Assert.True (subSubView.CanFocus);
view.SetFocus ();
Assert.False (view.HasFocus);
subView.SetFocus ();
Assert.False (subView.HasFocus);
subSubView.SetFocus ();
Assert.False (subSubView.HasFocus);
}
[Fact]
public void CanFocus_False_Prevents_SubView_HasFocus ()
{
var view = new View { };
var subView = new View { CanFocus = true };
var subSubView = new View { };
subView.Add (subSubView);
view.Add (subView);
Assert.False (view.CanFocus);
Assert.True (subView.CanFocus);
Assert.False (subSubView.CanFocus);
view.SetFocus ();
Assert.False (view.HasFocus);
subView.SetFocus ();
Assert.False (subView.HasFocus);
subSubView.SetFocus ();
Assert.False (subSubView.HasFocus);
}
[Fact]
public void CanFocus_Set_True_No_SuperView_Doesnt_Set_HasFocus ()
{
var view = new View { };
// Act
view.CanFocus = true;
Assert.False (view.HasFocus);
}
[Fact]
public void CanFocus_Set_True_Sets_HasFocus_To_True ()
{
var view = new View { };
var subView = new View { };
view.Add (subView);
Assert.False (view.CanFocus);
Assert.False (subView.CanFocus);
view.SetFocus ();
Assert.False (view.HasFocus);
Assert.False (subView.HasFocus);
view.CanFocus = true;
view.SetFocus ();
Assert.True (view.HasFocus);
// Act
subView.CanFocus = true;
Assert.True (subView.HasFocus);
}
[Fact]
public void CanFocus_Set_SubView_True_Sets_HasFocus_To_True ()
{
var view = new View
{
CanFocus = true
};
var subView = new View
{
CanFocus = false
};
var subSubView = new View
{
CanFocus = true
};
subView.Add (subSubView);
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.False (subView.HasFocus);
Assert.False (subSubView.HasFocus);
// Act
subView.CanFocus = true;
Assert.True (subView.HasFocus);
Assert.True (subSubView.HasFocus);
}
[Fact]
public void CanFocus_Set_SubView_True_Does_Not_Change_Focus_If_SuperView_Focused_Is_True ()
{
var top = new View
{
Id = "top",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
var subSubView = new View
{
Id = "subSubView",
CanFocus = true
};
subView.Add (subSubView);
var subView2 = new View
{
Id = "subView2",
CanFocus = false
};
top.Add (subView, subView2);
top.SetFocus ();
Assert.True (top.HasFocus);
Assert.Equal (subView, top.Focused);
Assert.True (subView.HasFocus);
Assert.True (subSubView.HasFocus);
// Act
subView2.CanFocus = true;
Assert.False (subView2.HasFocus);
Assert.True (subView.HasFocus);
Assert.True (subSubView.HasFocus);
}
[Fact]
public void CanFocus_Set_False_Sets_HasFocus_To_False ()
{
var view = new View { CanFocus = true };
var view2 = new View { CanFocus = true };
view2.Add (view);
Assert.True (view.CanFocus);
view.SetFocus ();
Assert.True (view.HasFocus);
view.CanFocus = false;
Assert.False (view.CanFocus);
Assert.False (view.HasFocus);
}
// TODO: Figure out what this test is supposed to be testing
[Fact]
public void CanFocus_Faced_With_Container ()
{
var t = new Toplevel ();
var w = new Window ();
var f = new FrameView ();
var v = new View { CanFocus = true };
f.Add (v);
w.Add (f);
t.Add (w);
Assert.True (t.CanFocus);
Assert.True (w.CanFocus);
Assert.True (f.CanFocus);
Assert.True (v.CanFocus);
f.CanFocus = false;
Assert.False (f.CanFocus);
Assert.True (v.CanFocus);
v.CanFocus = false;
Assert.False (f.CanFocus);
Assert.False (v.CanFocus);
v.CanFocus = true;
Assert.False (f.CanFocus);
Assert.True (v.CanFocus);
}
// TODO: Figure out what this test is supposed to be testing
[Fact]
public void CanFocus_Faced_With_Container_Before_Run ()
{
Application.Init (new FakeDriver ());
Toplevel t = new ();
var w = new Window ();
var f = new FrameView ();
var v = new View { CanFocus = true };
f.Add (v);
w.Add (f);
t.Add (w);
Assert.True (t.CanFocus);
Assert.True (w.CanFocus);
Assert.True (f.CanFocus);
Assert.True (v.CanFocus);
f.CanFocus = false;
Assert.False (f.CanFocus);
Assert.True (v.CanFocus);
v.CanFocus = false;
Assert.False (f.CanFocus);
Assert.False (v.CanFocus);
v.CanFocus = true;
Assert.False (f.CanFocus);
Assert.True (v.CanFocus);
Application.Iteration += (s, a) => Application.RequestStop ();
Application.Run (t);
t.Dispose ();
Application.Shutdown ();
}
//[Fact]
//public void CanFocus_Set_Changes_TabIndex_And_TabStop ()
//{
// var r = new View ();
// var v1 = new View { Text = "1" };
// var v2 = new View { Text = "2" };
// var v3 = new View { Text = "3" };
// r.Add (v1, v2, v3);
// v2.CanFocus = true;
// Assert.Equal (r.TabIndexes.IndexOf (v2), v2.TabIndex);
// Assert.Equal (0, v2.TabIndex);
// Assert.Equal (TabBehavior.TabStop, v2.TabStop);
// v1.CanFocus = true;
// Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex);
// Assert.Equal (1, v1.TabIndex);
// Assert.Equal (TabBehavior.TabStop, v1.TabStop);
// v1.TabIndex = 2;
// Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex);
// Assert.Equal (1, v1.TabIndex);
// v3.CanFocus = true;
// Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex);
// Assert.Equal (1, v1.TabIndex);
// Assert.Equal (r.TabIndexes.IndexOf (v3), v3.TabIndex);
// Assert.Equal (2, v3.TabIndex);
// Assert.Equal (TabBehavior.TabStop, v3.TabStop);
// v2.CanFocus = false;
// Assert.Equal (r.TabIndexes.IndexOf (v1), v1.TabIndex);
// Assert.Equal (1, v1.TabIndex);
// Assert.Equal (TabBehavior.TabStop, v1.TabStop);
// Assert.Equal (r.TabIndexes.IndexOf (v2), v2.TabIndex); // TabIndex is not changed
// Assert.NotEqual (-1, v2.TabIndex);
// Assert.Equal (TabBehavior.TabStop, v2.TabStop); // TabStop is not changed
// Assert.Equal (r.TabIndexes.IndexOf (v3), v3.TabIndex);
// Assert.Equal (2, v3.TabIndex);
// Assert.Equal (TabBehavior.TabStop, v3.TabStop);
// r.Dispose ();
//}
[Fact]
public void CanFocus_True_Focuses ()
{
View view = new ()
{
Id = "view"
};
View superView = new ()
{
Id = "superView",
CanFocus = true
};
superView.Add (view);
superView.SetFocus ();
Assert.True (superView.HasFocus);
Assert.NotEqual (view, superView.Focused);
view.CanFocus = true;
Assert.True (superView.HasFocus);
Assert.Equal (view, superView.Focused);
Assert.True (view.HasFocus);
view.CanFocus = false;
Assert.True (superView.HasFocus);
Assert.NotEqual (view, superView.Focused);
Assert.False (view.HasFocus);
}
[Fact]
public void CanFocus_Set_True_Get_AdvanceFocus_Works ()
{
Label label = new () { Text = "label" };
View view = new () { Text = "view", CanFocus = true };
Application.Navigation = new ();
Application.Top = new ();
Application.Top.Add (label, view);
Application.Top.SetFocus ();
Assert.Equal (view, Application.Navigation.GetFocused ());
Assert.False (label.CanFocus);
Assert.False (label.HasFocus);
Assert.True (view.CanFocus);
Assert.True (view.HasFocus);
Assert.False (Application.Navigation.AdvanceFocus (NavigationDirection.Forward, null));
Assert.False (label.HasFocus);
Assert.True (view.HasFocus);
// Set label CanFocus to true
label.CanFocus = true;
Assert.False (label.HasFocus);
Assert.True (view.HasFocus);
// label can now be focused, so AdvanceFocus should move to it.
Assert.True (Application.Navigation.AdvanceFocus (NavigationDirection.Forward, null));
Assert.True (label.HasFocus);
Assert.False (view.HasFocus);
// Move back to view
view.SetFocus ();
Assert.False (label.HasFocus);
Assert.True (view.HasFocus);
Assert.True (Application.RaiseKeyDownEvent (Key.Tab));
Assert.True (label.HasFocus);
Assert.False (view.HasFocus);
Application.Top.Dispose ();
Application.ResetState ();
}
}

View File

@@ -0,0 +1,319 @@
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class EnabledTests () : TestsAllViews
{
[Fact]
public void Enabled_False_Leaves ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
view.SetFocus ();
Assert.True (view.HasFocus);
view.Enabled = false;
Assert.False (view.HasFocus);
}
[Fact]
public void Enabled_False_Leaves_Subview ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
view.Enabled = false;
Assert.False (view.HasFocus);
Assert.False (subView.HasFocus);
}
[Fact]
public void Enabled_False_Leaves_Subview2 ()
{
var view = new Window
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
view.Enabled = false;
Assert.False (view.HasFocus);
Assert.False (subView.HasFocus);
}
[Fact]
public void Enabled_False_On_Subview_Leaves_Just_Subview ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
subView.Enabled = false;
Assert.True (view.HasFocus);
Assert.False (subView.HasFocus);
}
[Fact]
public void Enabled_False_Focuses_Deepest_Focusable_Subview ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
var subViewSubView1 = new View
{
Id = "subViewSubView1",
CanFocus = false
};
var subViewSubView2 = new View
{
Id = "subViewSubView2",
CanFocus = true
};
var subViewSubView3 = new View
{
Id = "subViewSubView3",
CanFocus = true // This is the one that will be focused
};
subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
view.Add (subView);
view.SetFocus ();
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
Assert.Equal (subViewSubView2, subView.Focused);
subViewSubView2.Enabled = false;
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
Assert.Equal (subViewSubView3, subView.Focused);
Assert.True (subViewSubView3.HasFocus);
}
[Fact]
public void Enabled_True_Subview_Focuses_SubView ()
{
var view = new View
{
Id = "view",
CanFocus = true,
Enabled = false
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
view.Add (subView);
view.SetFocus ();
Assert.False (view.HasFocus);
Assert.False (subView.HasFocus);
view.Enabled = true;
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
}
[Fact]
public void Enabled_True_On_Subview_Focuses ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true,
Enabled = false
};
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.False (subView.HasFocus);
subView.Enabled = true;
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
}
[Fact]
public void Enabled_True_Focuses_Deepest_Focusable_Subview ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true,
Enabled = false
};
var subViewSubView1 = new View
{
Id = "subViewSubView1",
CanFocus = false
};
var subViewSubView2 = new View
{
Id = "subViewSubView2",
CanFocus = true // This is the one that will be focused
};
var subViewSubView3 = new View
{
Id = "subViewSubView3",
CanFocus = true
};
subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
view.Add (subView);
view.SetFocus ();
Assert.False (subView.HasFocus);
Assert.False (subViewSubView2.HasFocus);
subView.Enabled = true;
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
Assert.Equal (subViewSubView2, subView.Focused);
Assert.True (subViewSubView2.HasFocus);
}
[Fact]
[AutoInitShutdown]
public void _Enabled_Sets_Also_Sets_Subviews ()
{
var wasClicked = false;
var button = new Button { Text = "Click Me" };
button.IsDefault = true;
button.Accepting += (s, e) => wasClicked = !wasClicked;
var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () };
win.Add (button);
var top = new Toplevel ();
top.Add (win);
var iterations = 0;
Application.Iteration += (s, a) =>
{
iterations++;
win.NewKeyDownEvent (Key.Enter);
Assert.True (wasClicked);
button.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked });
Assert.False (wasClicked);
Assert.True (button.Enabled);
Assert.True (button.CanFocus);
Assert.True (button.HasFocus);
Assert.True (win.Enabled);
Assert.True (win.CanFocus);
Assert.True (win.HasFocus);
Assert.True (button.HasFocus);
win.Enabled = false;
Assert.False (button.HasFocus);
button.NewKeyDownEvent (Key.Enter);
Assert.False (wasClicked);
button.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked });
Assert.False (wasClicked);
Assert.False (button.Enabled);
Assert.True (button.CanFocus);
Assert.False (button.HasFocus);
Assert.False (win.Enabled);
Assert.True (win.CanFocus);
Assert.False (win.HasFocus);
button.SetFocus ();
Assert.False (button.HasFocus);
Assert.False (win.HasFocus);
win.SetFocus ();
Assert.False (button.HasFocus);
Assert.False (win.HasFocus);
win.Enabled = true;
win.FocusDeepest (NavigationDirection.Forward, null);
Assert.True (button.HasFocus);
Assert.True (win.HasFocus);
Application.RequestStop ();
};
Application.Run (top);
Assert.Equal (1, iterations);
top.Dispose ();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,234 @@
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class HasFocusTests () : TestsAllViews
{
[Fact]
public void HasFocus_False ()
{
var view = new View ()
{
Id = "view",
CanFocus = true
};
view.SetFocus ();
Assert.True (view.HasFocus);
view.HasFocus = false;
Assert.False (view.HasFocus);
}
[Fact]
public void HasFocus_False_WithSuperView_Does_Not_Change_SuperView ()
{
var view = new View ()
{
Id = "view",
CanFocus = true
};
var subview = new View ()
{
Id = "subview",
CanFocus = true
};
view.Add (subview);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subview.HasFocus);
subview.HasFocus = false;
Assert.False (subview.HasFocus);
Assert.True (view.HasFocus);
}
[Fact]
public void HasFocus_False_WithSubview_Leaves_All ()
{
var view = new View ()
{
Id = "view",
CanFocus = true
};
var subview = new View ()
{
Id = "subview",
CanFocus = true
};
view.Add (subview);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subview.HasFocus);
Assert.Equal (subview, view.Focused);
view.HasFocus = false;
Assert.Null (view.Focused);
Assert.False (view.HasFocus);
Assert.False (subview.HasFocus);
}
[Fact]
public void Enabled_False_Sets_HasFocus_To_False ()
{
var wasClicked = false;
var view = new Button { Text = "Click Me" };
view.Accepting += (s, e) => wasClicked = !wasClicked;
view.NewKeyDownEvent (Key.Space);
Assert.True (wasClicked);
view.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked });
Assert.False (wasClicked);
Assert.True (view.Enabled);
Assert.True (view.CanFocus);
Assert.True (view.HasFocus);
view.Enabled = false;
view.NewKeyDownEvent (Key.Space);
Assert.False (wasClicked);
view.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked });
Assert.False (wasClicked);
Assert.False (view.Enabled);
Assert.True (view.CanFocus);
Assert.False (view.HasFocus);
view.SetFocus ();
Assert.False (view.HasFocus);
}
[Fact]
public void HasFocus_False_CompoundSubView_Leaves_All ()
{
var view = new View ()
{
Id = "view",
CanFocus = true
};
var subView = new View ()
{
Id = "subView",
CanFocus = true
};
var subViewSubView1 = new View ()
{
Id = "subViewSubView1",
CanFocus = false
};
var subViewSubView2 = new View ()
{
Id = "subViewSubView2",
CanFocus = true
};
var subViewSubView3 = new View ()
{
Id = "subViewSubView3",
CanFocus = false
};
subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.False (subViewSubView1.HasFocus);
Assert.True (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
view.HasFocus = false;
Assert.False (view.HasFocus);
Assert.False (subView.HasFocus);
Assert.False (subViewSubView1.HasFocus);
Assert.False (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
}
[Fact]
public void HasFocus_False_Subview_Raises_HasFocusChanged ()
{
var top = new View
{
Id = "top",
CanFocus = true
};
var subView1 = new View
{
Id = "subView1",
CanFocus = true
};
var subView2 = new View
{
Id = "subView2",
CanFocus = true
};
top.Add (subView1, subView2);
var subView1HasFocusChangedTrueCount = 0;
var subView1HasFocusChangedFalseCount = 0;
subView1.HasFocusChanged += (s, e) =>
{
if (e.NewValue)
{
subView1HasFocusChangedTrueCount++;
}
else
{
subView1HasFocusChangedFalseCount++;
}
};
var subView2HasFocusChangedTrueCount = 0;
var subView2HasFocusChangedFalseCount = 0;
subView2.HasFocusChanged += (s, e) =>
{
if (e.NewValue)
{
subView2HasFocusChangedTrueCount++;
}
else
{
subView2HasFocusChangedFalseCount++;
}
};
top.SetFocus ();
Assert.True (top.HasFocus);
Assert.True (subView1.HasFocus);
Assert.False (subView2.HasFocus);
Assert.Equal (1, subView1HasFocusChangedTrueCount);
Assert.Equal (0, subView1HasFocusChangedFalseCount);
Assert.Equal (0, subView2HasFocusChangedTrueCount);
Assert.Equal (0, subView2HasFocusChangedFalseCount);
subView1.HasFocus = false; // this should have the same resuilt as top.AdvanceFocus (NavigationDirection.Forward, null);
Assert.False (subView1.HasFocus);
Assert.True (subView2.HasFocus);
Assert.Equal (1, subView1HasFocusChangedTrueCount);
Assert.Equal (1, subView1HasFocusChangedFalseCount);
Assert.Equal (1, subView2HasFocusChangedTrueCount);
Assert.Equal (0, subView2HasFocusChangedFalseCount);
}
}

View File

@@ -0,0 +1,426 @@
using JetBrains.Annotations;
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class NavigationTests (ITestOutputHelper _output) : TestsAllViews
{
[Theory]
[MemberData (nameof (AllViewTypes))]
[SetupFakeDriver] // SetupFakeDriver resets app state; helps to avoid test pollution
public void AllViews_AtLeastOneNavKey_Advances (Type viewType)
{
View view = CreateInstanceIfNotGeneric (viewType);
if (view == null)
{
_output.WriteLine ($"Ignoring {viewType} - It's a Generic");
return;
}
if (!view.CanFocus)
{
_output.WriteLine ($"Ignoring {viewType} - It can't focus.");
return;
}
Toplevel top = new ();
Application.Top = top;
Application.Navigation = new ApplicationNavigation ();
View otherView = new ()
{
Id = "otherView",
CanFocus = true,
TabStop = view.TabStop == TabBehavior.NoStop ? TabBehavior.TabStop : view.TabStop
};
top.Add (view, otherView);
// Start with the focus on our test view
view.SetFocus ();
Key [] navKeys = [Key.Tab, Key.Tab.WithShift, Key.CursorUp, Key.CursorDown, Key.CursorLeft, Key.CursorRight];
if (view.TabStop == TabBehavior.TabGroup)
{
navKeys = new [] { Key.F6, Key.F6.WithShift };
}
var left = false;
foreach (Key key in navKeys)
{
switch (view.TabStop)
{
case TabBehavior.TabStop:
case TabBehavior.NoStop:
case TabBehavior.TabGroup:
Application.RaiseKeyDownEvent (key);
if (view.HasFocus)
{
// Try once more (HexView)
Application.RaiseKeyDownEvent (key);
}
break;
default:
Application.RaiseKeyDownEvent (Key.Tab);
break;
}
if (!view.HasFocus)
{
left = true;
_output.WriteLine ($"{view.GetType ().Name} - {key} Left.");
break;
}
_output.WriteLine ($"{view.GetType ().Name} - {key} did not Leave.");
}
top.Dispose ();
Application.ResetState ();
Assert.True (left);
}
[Theory]
[MemberData (nameof (AllViewTypes))]
[SetupFakeDriver] // SetupFakeDriver resets app state; helps to avoid test pollution
public void AllViews_HasFocus_Changed_Event (Type viewType)
{
View view = CreateInstanceIfNotGeneric (viewType);
if (view == null)
{
_output.WriteLine ($"Ignoring {viewType} - It's a Generic");
return;
}
if (!view.CanFocus)
{
_output.WriteLine ($"Ignoring {viewType} - It can't focus.");
return;
}
if (view is Toplevel && ((Toplevel)view).Modal)
{
_output.WriteLine ($"Ignoring {viewType} - It's a Modal Toplevel");
return;
}
Toplevel top = new ();
Application.Top = top;
Application.Navigation = new ApplicationNavigation ();
View otherView = new ()
{
Id = "otherView",
CanFocus = true,
TabStop = view.TabStop == TabBehavior.NoStop ? TabBehavior.TabStop : view.TabStop
};
var hasFocusTrue = 0;
var hasFocusFalse = 0;
view.HasFocusChanged += (s, e) =>
{
if (e.NewValue)
{
hasFocusTrue++;
}
else
{
hasFocusFalse++;
}
};
top.Add (view, otherView);
Assert.False (view.HasFocus);
Assert.False (otherView.HasFocus);
// Ensure the view is Visible
view.Visible = true;
Application.Top.SetFocus ();
Assert.True (Application.Top!.HasFocus);
Assert.True (top.HasFocus);
// Start with the focus on our test view
Assert.True (view.HasFocus);
Assert.Equal (1, hasFocusTrue);
Assert.Equal (0, hasFocusFalse);
// Use keyboard to navigate to next view (otherView).
var tries = 0;
while (view.HasFocus)
{
if (++tries > 10)
{
Assert.Fail ($"{view} is not leaving.");
}
switch (view.TabStop)
{
case null:
case TabBehavior.NoStop:
case TabBehavior.TabStop:
if (Application.RaiseKeyDownEvent (Key.Tab))
{
if (view.HasFocus)
{
// Try another nav key (e.g. for TextView that eats Tab)
Application.RaiseKeyDownEvent (Key.CursorDown);
}
};
break;
case TabBehavior.TabGroup:
Application.RaiseKeyDownEvent (Key.F6);
break;
default:
throw new ArgumentOutOfRangeException ();
}
}
Assert.Equal (1, hasFocusTrue);
Assert.Equal (1, hasFocusFalse);
Assert.False (view.HasFocus);
Assert.True (otherView.HasFocus);
// Now navigate back to our test view
switch (view.TabStop)
{
case TabBehavior.NoStop:
view.SetFocus ();
break;
case TabBehavior.TabStop:
Application.RaiseKeyDownEvent (Key.Tab);
break;
case TabBehavior.TabGroup:
if (!Application.RaiseKeyDownEvent (Key.F6))
{
view.SetFocus ();
}
break;
case null:
Application.RaiseKeyDownEvent (Key.Tab);
break;
default:
throw new ArgumentOutOfRangeException ();
}
Assert.Equal (2, hasFocusTrue);
Assert.Equal (1, hasFocusFalse);
Assert.True (view.HasFocus);
Assert.False (otherView.HasFocus);
// Cache state because Shutdown has side effects.
// Also ensures other tests can continue running if there's a fail
bool otherViewHasFocus = otherView.HasFocus;
bool viewHasFocus = view.HasFocus;
int enterCount = hasFocusTrue;
int leaveCount = hasFocusFalse;
top.Dispose ();
Assert.False (otherViewHasFocus);
Assert.True (viewHasFocus);
Assert.Equal (2, enterCount);
Assert.Equal (1, leaveCount);
Application.ResetState ();
}
[Theory]
[MemberData (nameof (AllViewTypes))]
[SetupFakeDriver] // SetupFakeDriver resets app state; helps to avoid test pollution
public void AllViews_Visible_False_No_HasFocus_Events (Type viewType)
{
View view = CreateInstanceIfNotGeneric (viewType);
if (view == null)
{
_output.WriteLine ($"Ignoring {viewType} - It's a Generic");
return;
}
if (!view.CanFocus)
{
_output.WriteLine ($"Ignoring {viewType} - It can't focus.");
return;
}
if (view is Toplevel && ((Toplevel)view).Modal)
{
_output.WriteLine ($"Ignoring {viewType} - It's a Modal Toplevel");
return;
}
Toplevel top = new ();
Application.Top = top;
Application.Navigation = new ApplicationNavigation ();
View otherView = new ()
{
CanFocus = true
};
view.Visible = false;
var hasFocusChangingCount = 0;
var hasFocusChangedCount = 0;
view.HasFocusChanging += (s, e) => hasFocusChangingCount++;
view.HasFocusChanged += (s, e) => hasFocusChangedCount++;
top.Add (view, otherView);
// Start with the focus on our test view
view.SetFocus ();
Assert.Equal (0, hasFocusChangingCount);
Assert.Equal (0, hasFocusChangedCount);
Application.RaiseKeyDownEvent (Key.Tab);
Assert.Equal (0, hasFocusChangingCount);
Assert.Equal (0, hasFocusChangedCount);
Application.RaiseKeyDownEvent (Key.F6);
Assert.Equal (0, hasFocusChangingCount);
Assert.Equal (0, hasFocusChangedCount);
top.Dispose ();
Application.ResetState ();
}
// View.Focused & View.MostFocused tests
// View.Focused - No subviews
[Fact]
public void Focused_NoSubviews ()
{
var view = new View ();
Assert.Null (view.Focused);
view.CanFocus = true;
view.SetFocus ();
}
[Fact]
public void GetMostFocused_NoSubviews_Returns_Null ()
{
var view = new View ();
Assert.Null (view.Focused);
view.CanFocus = true;
Assert.False (view.HasFocus);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.Null (view.MostFocused);
}
[Fact]
public void GetMostFocused_Returns_Most ()
{
var view = new View ()
{
Id = "view",
CanFocus = true
};
var subview = new View ()
{
Id = "subview",
CanFocus = true
};
view.Add (subview);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subview.HasFocus);
Assert.Equal (subview, view.MostFocused);
var subview2 = new View ()
{
Id = "subview2",
CanFocus = true
};
view.Add (subview2);
Assert.Equal (subview2, view.MostFocused);
}
[Fact]
[SetupFakeDriver]
public void Navigation_With_Null_Focused_View ()
{
// Non-regression test for #882 (NullReferenceException during keyboard navigation when Focused is null)
Application.Init (new FakeDriver ());
var top = new Toplevel ();
top.Ready += (s, e) => { Assert.Null (top.Focused); };
// Keyboard navigation with tab
FakeConsole.MockKeyPresses.Push (new ('\t', ConsoleKey.Tab, false, false, false));
Application.Iteration += (s, a) => Application.RequestStop ();
Application.Run (top);
top.Dispose ();
Application.Shutdown ();
}
[Fact]
[AutoInitShutdown]
public void Application_Begin_FocusesDeepest ()
{
var win1 = new Window { Id = "win1", Width = 10, Height = 1 };
var view1 = new View { Id = "view1", Width = Dim.Fill (), Height = Dim.Fill (), CanFocus = true };
var win2 = new Window { Id = "win2", Y = 6, Width = 10, Height = 1 };
var view2 = new View { Id = "view2", Width = Dim.Fill (), Height = Dim.Fill (), CanFocus = true };
win2.Add (view2);
win1.Add (view1, win2);
Application.Begin (win1);
Assert.True (win1.HasFocus);
Assert.True (view1.HasFocus);
Assert.False (win2.HasFocus);
Assert.False (view2.HasFocus);
win1.Dispose ();
}
}

View File

@@ -0,0 +1,174 @@
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class RestoreFocusTests () : TestsAllViews
{
[Fact]
public void RestoreFocus_Restores ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
var subViewSubView1 = new View
{
Id = "subViewSubView1",
CanFocus = true
};
var subViewSubView2 = new View
{
Id = "subViewSubView2",
CanFocus = true
};
var subViewSubView3 = new View
{
Id = "subViewSubView3",
CanFocus = true
};
subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
Assert.True (subViewSubView1.HasFocus);
Assert.Equal (subViewSubView1, subView.Focused);
view.HasFocus = false;
Assert.False (view.HasFocus);
Assert.False (subView.HasFocus);
Assert.False (subViewSubView1.HasFocus);
Assert.False (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
view.RestoreFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
Assert.Equal (subViewSubView1, subView.Focused);
Assert.True (subViewSubView1.HasFocus);
Assert.False (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
subViewSubView2.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.False (subViewSubView1.HasFocus);
Assert.True (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
view.HasFocus = false;
Assert.False (view.HasFocus);
Assert.False (subView.HasFocus);
Assert.False (subViewSubView1.HasFocus);
Assert.False (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
view.RestoreFocus ();
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
Assert.True (subViewSubView2.HasFocus);
Assert.Equal (subViewSubView2, subView.Focused);
Assert.False (subViewSubView1.HasFocus);
Assert.False (subViewSubView3.HasFocus);
}
[Fact]
public void RestoreFocus_Across_TabGroup ()
{
var top = new View
{
Id = "top",
CanFocus = true,
TabStop = TabBehavior.TabGroup
};
var tabGroup1 = new View
{
Id = "tabGroup1",
CanFocus = true,
TabStop = TabBehavior.TabGroup
};
var tabGroup1SubView1 = new View
{
Id = "tabGroup1SubView1",
CanFocus = true,
TabStop = TabBehavior.TabStop
};
var tabGroup1SubView2 = new View
{
Id = "tabGroup1SubView2",
CanFocus = true,
TabStop = TabBehavior.TabStop
};
tabGroup1.Add (tabGroup1SubView1, tabGroup1SubView2);
var tabGroup2 = new View
{
Id = "tabGroup2",
CanFocus = true,
TabStop = TabBehavior.TabGroup
};
var tabGroup2SubView1 = new View
{
Id = "tabGroup2SubView1",
CanFocus = true,
TabStop = TabBehavior.TabStop
};
var tabGroup2SubView2 = new View
{
Id = "tabGroup2SubView2",
CanFocus = true,
TabStop = TabBehavior.TabStop
};
tabGroup2.Add (tabGroup2SubView1, tabGroup2SubView2);
top.Add (tabGroup1, tabGroup2);
top.SetFocus ();
Assert.True (top.HasFocus);
Assert.Equal (tabGroup1, top.Focused);
Assert.Equal (tabGroup1SubView1, tabGroup1.Focused);
top.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
Assert.True (top.HasFocus);
Assert.Equal (tabGroup2, top.Focused);
Assert.Equal (tabGroup2SubView1, tabGroup2.Focused);
top.HasFocus = false;
Assert.False (top.HasFocus);
top.RestoreFocus ();
Assert.True (top.HasFocus);
Assert.Equal (tabGroup2, top.Focused);
Assert.Equal (tabGroup2SubView1, tabGroup2.Focused);
top.HasFocus = false;
Assert.False (top.HasFocus);
top.RestoreFocus ();
Assert.True (top.HasFocus);
Assert.Equal (tabGroup2, top.Focused);
Assert.Equal (tabGroup2SubView1, tabGroup2.Focused);
}
}

View File

@@ -0,0 +1,396 @@
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class SetFocusTests () : TestsAllViews
{
[Fact]
public void SetFocus_With_Null_Superview_Does_Not_Throw_Exception ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
Assert.True (view.CanFocus);
Assert.False (view.HasFocus);
Exception exception = Record.Exception (() => view.SetFocus ());
Assert.Null (exception);
Assert.True (view.CanFocus);
Assert.True (view.HasFocus);
}
[Fact]
public void SetFocus_SetsFocus ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
Assert.True (view.CanFocus);
Assert.False (view.HasFocus);
view.SetFocus ();
Assert.True (view.HasFocus);
}
[Fact]
public void SetFocus_NoSubView_Focused_Is_Null ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
Assert.True (view.CanFocus);
Assert.False (view.HasFocus);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.Null (view.Focused);
}
[Fact]
public void SetFocus_SubView_Focused_Is_Set ()
{
var view = new Window
{
Id = "view",
CanFocus = true
};
var subview = new View
{
Id = "subview",
CanFocus = true
};
view.Add (subview);
Assert.True (view.CanFocus);
Assert.False (view.HasFocus);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.Equal (subview, view.Focused);
}
[Fact]
public void SetFocus_SetsFocus_DeepestSubView ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subview = new View
{
Id = "subview",
CanFocus = true
};
view.Add (subview);
view.SetFocus ();
Assert.True (subview.HasFocus);
Assert.Equal (subview, view.Focused);
}
[Fact]
public void SetFocus_SetsFocus_DeepestSubView_CompoundSubView ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
var subViewSubView1 = new View
{
Id = "subViewSubView1",
CanFocus = false
};
var subViewSubView2 = new View
{
Id = "subViewSubView2",
CanFocus = true
};
var subViewSubView3 = new View
{
Id = "subViewSubView3",
CanFocus = false
};
subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
view.Add (subView);
view.SetFocus ();
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
Assert.Equal (subViewSubView2, subView.Focused);
}
[Fact]
public void SetFocus_CompoundSubView_SetFocus_Sets ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
var subViewSubView1 = new View
{
Id = "subViewSubView1",
CanFocus = true
};
var subViewSubView2 = new View
{
Id = "subViewSubView2",
CanFocus = true
};
var subViewSubView3 = new View
{
Id = "subViewSubView3",
CanFocus = true
};
subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
Assert.True (subViewSubView1.HasFocus);
Assert.Equal (subViewSubView1, subView.Focused);
subViewSubView2.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.False (subViewSubView1.HasFocus);
Assert.True (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
}
[Fact]
public void SetFocus_AdornmentSubView_SetFocus_Sets ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
view.Add (subView);
var borderSubView = new View
{
Id = "borderSubView",
CanFocus = true
};
var subViewSubView1 = new View
{
Id = "subViewSubView1",
CanFocus = true
};
var subViewSubView2 = new View
{
Id = "subViewSubView2",
CanFocus = true
};
var subViewSubView3 = new View
{
Id = "subViewSubView3",
CanFocus = true
};
borderSubView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
view.Border.Add (borderSubView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.False (borderSubView.HasFocus);
view.Border.CanFocus = true;
subViewSubView1.SetFocus ();
Assert.True (view.HasFocus);
Assert.False (subView.HasFocus);
Assert.True (borderSubView.HasFocus);
Assert.True (subViewSubView1.HasFocus);
Assert.False (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
view.Border.CanFocus = false;
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.False (view.Border.HasFocus);
Assert.False (borderSubView.HasFocus);
Assert.False (subViewSubView1.HasFocus);
Assert.False (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
view.Border.CanFocus = true;
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.False (view.Border.HasFocus);
Assert.False (borderSubView.HasFocus);
Assert.False (subViewSubView1.HasFocus);
Assert.False (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
view.Border.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (view.Border.HasFocus);
Assert.False (subView.HasFocus);
Assert.True (borderSubView.HasFocus);
Assert.True (subViewSubView1.HasFocus);
Assert.False (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
view.Border.CanFocus = false;
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.False (view.Border.HasFocus);
Assert.False (borderSubView.HasFocus);
Assert.False (subViewSubView1.HasFocus);
Assert.False (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
view.Border.CanFocus = true;
subViewSubView1.SetFocus ();
Assert.True (view.HasFocus);
Assert.False (subView.HasFocus);
Assert.True (view.Border.HasFocus);
Assert.True (borderSubView.HasFocus);
Assert.True (subViewSubView1.HasFocus);
Assert.False (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
subView.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.False (view.Border.HasFocus);
Assert.False (borderSubView.HasFocus);
Assert.False (subViewSubView1.HasFocus);
Assert.False (subViewSubView2.HasFocus);
Assert.False (subViewSubView3.HasFocus);
}
[Fact]
public void SetFocus_Peer_LeavesOther ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subview1 = new View
{
Id = "subview1",
CanFocus = true
};
var subview2 = new View
{
Id = "subview2",
CanFocus = true
};
view.Add (subview1, subview2);
view.SetFocus ();
Assert.Equal (subview1, view.Focused);
Assert.True (subview1.HasFocus);
Assert.False (subview2.HasFocus);
subview2.SetFocus ();
Assert.Equal (subview2, view.Focused);
Assert.True (subview2.HasFocus);
Assert.False (subview1.HasFocus);
}
[Fact]
public void SetFocus_On_Peer_Moves_Focus_To_Peer ()
{
var top = new View
{
Id = "top",
CanFocus = true
};
var view1 = new View
{
Id = "view1",
CanFocus = true
};
var subView1 = new View
{
Id = "subView1",
CanFocus = true
};
view1.Add (subView1);
var subView1SubView1 = new View
{
Id = "subView1subView1",
CanFocus = true
};
subView1.Add (subView1SubView1);
var view2 = new View
{
Id = "view2",
CanFocus = true
};
top.Add (view1, view2);
Assert.False (view1.HasFocus);
Assert.False (view2.HasFocus);
view1.SetFocus ();
Assert.True (view1.HasFocus);
Assert.True (subView1.HasFocus);
Assert.True (subView1SubView1.HasFocus);
Assert.Equal (subView1, view1.Focused);
Assert.Equal (subView1SubView1, subView1.Focused);
view2.SetFocus ();
Assert.False (view1.HasFocus);
Assert.True (view2.HasFocus);
}
}

View File

@@ -0,0 +1,254 @@
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class VisibleTests () : TestsAllViews
{
[Fact]
public void Visible_False_Leaves ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
view.SetFocus ();
Assert.True (view.HasFocus);
view.Visible = false;
Assert.False (view.HasFocus);
}
[Fact]
public void Visible_False_Leaves_Subview ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
view.Visible = false;
Assert.False (view.HasFocus);
Assert.False (subView.HasFocus);
}
[Fact]
public void Visible_False_Leaves_Subview2 ()
{
var view = new Window
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
view.Visible = false;
Assert.False (view.HasFocus);
Assert.False (subView.HasFocus);
}
[Fact]
public void Visible_False_On_Subview_Leaves_Just_Subview ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
subView.Visible = false;
Assert.True (view.HasFocus);
Assert.False (subView.HasFocus);
}
[Fact]
public void Visible_False_Focuses_Deepest_Focusable_Subview ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
var subViewSubView1 = new View
{
Id = "subViewSubView1",
CanFocus = false
};
var subViewSubView2 = new View
{
Id = "subViewSubView2",
CanFocus = true
};
var subViewSubView3 = new View
{
Id = "subViewSubView3",
CanFocus = true // This is the one that will be focused
};
subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
view.Add (subView);
view.SetFocus ();
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
Assert.Equal (subViewSubView2, subView.Focused);
subViewSubView2.Visible = false;
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
Assert.Equal (subViewSubView3, subView.Focused);
Assert.True (subViewSubView3.HasFocus);
}
[Fact]
public void Visible_True_Subview_Focuses_SubView ()
{
var view = new View
{
Id = "view",
CanFocus = true,
Visible = false
};
var subView = new View
{
Id = "subView",
CanFocus = true
};
view.Add (subView);
view.SetFocus ();
Assert.False (view.HasFocus);
Assert.False (subView.HasFocus);
view.Visible = true;
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
}
[Fact]
public void Visible_True_On_Subview_Focuses ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true,
Visible = false
};
view.Add (subView);
view.SetFocus ();
Assert.True (view.HasFocus);
Assert.False (subView.HasFocus);
subView.Visible = true;
Assert.True (view.HasFocus);
Assert.True (subView.HasFocus);
}
[Fact]
public void Visible_True_Focuses_Deepest_Focusable_Subview ()
{
var view = new View
{
Id = "view",
CanFocus = true
};
var subView = new View
{
Id = "subView",
CanFocus = true,
Visible = false
};
var subViewSubView1 = new View
{
Id = "subViewSubView1",
CanFocus = false
};
var subViewSubView2 = new View
{
Id = "subViewSubView2",
CanFocus = true // This is the one that will be focused
};
var subViewSubView3 = new View
{
Id = "subViewSubView3",
CanFocus = true
};
subView.Add (subViewSubView1, subViewSubView2, subViewSubView3);
view.Add (subView);
view.SetFocus ();
Assert.False (subView.HasFocus);
Assert.False (subViewSubView2.HasFocus);
subView.Visible = true;
Assert.True (subView.HasFocus);
Assert.Equal (subView, view.Focused);
Assert.Equal (subViewSubView2, subView.Focused);
Assert.True (subViewSubView2.HasFocus);
}
}

View File

@@ -0,0 +1,107 @@
using Moq;
namespace Terminal.Gui.ViewTests.OrientationTests;
public class OrientationHelperTests
{
[Fact]
public void Orientation_Set_NewValue_InvokesChangingAndChangedEvents ()
{
// Arrange
Mock<IOrientation> mockIOrientation = new Mock<IOrientation> ();
var orientationHelper = new OrientationHelper (mockIOrientation.Object);
var changingEventInvoked = 0;
var changedEventInvoked = 0;
orientationHelper.OrientationChanging += (sender, e) => { changingEventInvoked++; };
orientationHelper.OrientationChanged += (sender, e) => { changedEventInvoked++; };
// Act
orientationHelper.Orientation = Orientation.Vertical;
// Assert
Assert.Equal (1, changingEventInvoked);
Assert.Equal(1, changedEventInvoked);
}
[Fact]
public void Orientation_Set_NewValue_InvokesOnChangingAndOnChangedOverrides ()
{
// Arrange
Mock<IOrientation> mockIOrientation = new Mock<IOrientation> ();
var onChangingOverrideCalled = 0;
var onChangedOverrideCalled = 0;
mockIOrientation.Setup (x => x.OnOrientationChanging (It.IsAny<Orientation> (), It.IsAny<Orientation> ()))
.Callback (() => onChangingOverrideCalled++)
.Returns (false); // Ensure it doesn't cancel the change
mockIOrientation.Setup (x => x.OnOrientationChanged (It.IsAny<Orientation> ()))
.Callback (() => onChangedOverrideCalled++);
var orientationHelper = new OrientationHelper (mockIOrientation.Object);
// Act
orientationHelper.Orientation = Orientation.Vertical;
// Assert
Assert.Equal (1, onChangingOverrideCalled);
Assert.Equal (1, onChangedOverrideCalled);
}
[Fact]
public void Orientation_Set_SameValue_DoesNotInvokeChangingOrChangedEvents ()
{
// Arrange
Mock<IOrientation> mockIOrientation = new Mock<IOrientation> ();
var orientationHelper = new OrientationHelper (mockIOrientation.Object);
orientationHelper.Orientation = Orientation.Horizontal; // Set initial orientation
var changingEventInvoked = 0;
var changedEventInvoked = 0;
orientationHelper.OrientationChanging += (sender, e) => { changingEventInvoked++; };
orientationHelper.OrientationChanged += (sender, e) => { changedEventInvoked++; };
// Act
orientationHelper.Orientation = Orientation.Horizontal; // Set to the same value
// Assert
Assert.Equal (0, changingEventInvoked);
Assert.Equal (0, changedEventInvoked);
}
[Fact]
public void Orientation_Set_NewValue_OrientationChanging_CancellationPreventsChange ()
{
// Arrange
Mock<IOrientation> mockIOrientation = new Mock<IOrientation> ();
var orientationHelper = new OrientationHelper (mockIOrientation.Object);
orientationHelper.OrientationChanging += (sender, e) => { e.Cancel = true; }; // Cancel the change
// Act
orientationHelper.Orientation = Orientation.Vertical;
// Assert
Assert.Equal (Orientation.Horizontal, orientationHelper.Orientation); // Initial orientation is Horizontal
}
[Fact]
public void Orientation_Set_NewValue_OnOrientationChanging_CancelsChange ()
{
// Arrange
Mock<IOrientation> mockIOrientation = new Mock<IOrientation> ();
mockIOrientation.Setup (x => x.OnOrientationChanging (It.IsAny<Orientation> (), It.IsAny<Orientation> ()))
.Returns (true); // Override to return true, cancelling the change
var orientationHelper = new OrientationHelper (mockIOrientation.Object);
// Act
orientationHelper.Orientation = Orientation.Vertical;
// Assert
Assert.Equal (
Orientation.Horizontal,
orientationHelper.Orientation); // Initial orientation is Horizontal, and it should remain unchanged due to cancellation
}
}

View File

@@ -0,0 +1,136 @@
namespace Terminal.Gui.ViewTests.OrientationTests;
public class OrientationTests
{
private class CustomView : View, IOrientation
{
private readonly OrientationHelper _orientationHelper;
public CustomView ()
{
_orientationHelper = new (this);
Orientation = Orientation.Vertical;
_orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
_orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
}
public Orientation Orientation
{
get => _orientationHelper.Orientation;
set => _orientationHelper.Orientation = value;
}
public event EventHandler<CancelEventArgs<Orientation>> OrientationChanging;
public event EventHandler<EventArgs<Orientation>> OrientationChanged;
public bool CancelOnOrientationChanging { get; set; }
public bool OnOrientationChangingCalled { get; private set; }
public bool OnOrientationChangedCalled { get; private set; }
public bool OnOrientationChanging (Orientation currentOrientation, Orientation newOrientation)
{
OnOrientationChangingCalled = true;
// Custom logic before orientation changes
return CancelOnOrientationChanging; // Return true to cancel the change
}
public void OnOrientationChanged (Orientation newOrientation)
{
OnOrientationChangedCalled = true;
// Custom logic after orientation has changed
}
}
[Fact]
public void Orientation_Change_IsSuccessful ()
{
// Arrange
var customView = new CustomView ();
var orientationChanged = false;
customView.OrientationChanged += (sender, e) => orientationChanged = true;
// Act
customView.Orientation = Orientation.Horizontal;
// Assert
Assert.True (orientationChanged, "OrientationChanged event was not invoked.");
Assert.Equal (Orientation.Horizontal, customView.Orientation);
}
[Fact]
public void Orientation_Change_OrientationChanging_Set_Cancel_IsCancelled ()
{
// Arrange
var customView = new CustomView ();
customView.OrientationChanging += (sender, e) => e.Cancel = true; // Cancel the orientation change
var orientationChanged = false;
customView.OrientationChanged += (sender, e) => orientationChanged = true;
// Act
customView.Orientation = Orientation.Horizontal;
// Assert
Assert.False (orientationChanged, "OrientationChanged event was invoked despite cancellation.");
Assert.Equal (Orientation.Vertical, customView.Orientation); // Assuming Vertical is the default orientation
}
[Fact]
public void Orientation_Change_OnOrientationChanging_Return_True_IsCancelled ()
{
// Arrange
var customView = new CustomView ();
customView.CancelOnOrientationChanging = true; // Cancel the orientation change
var orientationChanged = false;
customView.OrientationChanged += (sender, e) => orientationChanged = true;
// Act
customView.Orientation = Orientation.Horizontal;
// Assert
Assert.False (orientationChanged, "OrientationChanged event was invoked despite cancellation.");
Assert.Equal (Orientation.Vertical, customView.Orientation); // Assuming Vertical is the default orientation
}
[Fact]
public void OrientationChanging_VirtualMethodCalledBeforeEvent ()
{
// Arrange
var radioGroup = new CustomView ();
bool eventCalled = false;
radioGroup.OrientationChanging += (sender, e) =>
{
eventCalled = true;
Assert.True (radioGroup.OnOrientationChangingCalled, "OnOrientationChanging was not called before the event.");
};
// Act
radioGroup.Orientation = Orientation.Horizontal;
// Assert
Assert.True (eventCalled, "OrientationChanging event was not called.");
}
[Fact]
public void OrientationChanged_VirtualMethodCalledBeforeEvent ()
{
// Arrange
var radioGroup = new CustomView ();
bool eventCalled = false;
radioGroup.OrientationChanged += (sender, e) =>
{
eventCalled = true;
Assert.True (radioGroup.OnOrientationChangedCalled, "OnOrientationChanged was not called before the event.");
};
// Act
radioGroup.Orientation = Orientation.Horizontal;
// Assert
Assert.True (eventCalled, "OrientationChanged event was not called.");
}
}

View File

@@ -0,0 +1,288 @@
using System.Text;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class SubviewTests
{
private readonly ITestOutputHelper _output;
public SubviewTests (ITestOutputHelper output) { _output = output; }
[Fact]
[AutoInitShutdown]
public void GetTopSuperView_Test ()
{
var v1 = new View ();
var fv1 = new FrameView ();
fv1.Add (v1);
var tf1 = new TextField ();
var w1 = new Window ();
w1.Add (fv1, tf1);
var top1 = new Toplevel ();
top1.Add (w1);
var v2 = new View ();
var fv2 = new FrameView ();
fv2.Add (v2);
var tf2 = new TextField ();
var w2 = new Window ();
w2.Add (fv2, tf2);
var top2 = new Toplevel ();
top2.Add (w2);
Assert.Equal (top1, v1.GetTopSuperView ());
Assert.Equal (top2, v2.GetTopSuperView ());
v1.Dispose ();
fv1.Dispose ();
tf1.Dispose ();
w1.Dispose ();
top1.Dispose ();
v2.Dispose ();
fv2.Dispose ();
tf2.Dispose ();
w2.Dispose ();
top2.Dispose ();
}
[Fact]
[TestRespondersDisposed]
public void Initialized_Event_Comparing_With_Added_Event ()
{
Application.Init (new FakeDriver ());
var top = new Toplevel { Id = "0" }; // Frame: 0, 0, 80, 25; Viewport: 0, 0, 80, 25
var winAddedToTop = new Window
{
Id = "t", Width = Dim.Fill (), Height = Dim.Fill ()
}; // Frame: 0, 0, 80, 25; Viewport: 0, 0, 78, 23
var v1AddedToWin = new View
{
Id = "v1", Width = Dim.Fill (), Height = Dim.Fill ()
}; // Frame: 1, 1, 78, 23 (because Windows has a border)
var v2AddedToWin = new View
{
Id = "v2", Width = Dim.Fill (), Height = Dim.Fill ()
}; // Frame: 1, 1, 78, 23 (because Windows has a border)
var svAddedTov1 = new View
{
Id = "sv1", Width = Dim.Fill (), Height = Dim.Fill ()
}; // Frame: 1, 1, 78, 23 (same as it's superview v1AddedToWin)
int tc = 0, wc = 0, v1c = 0, v2c = 0, sv1c = 0;
winAddedToTop.Added += (s, e) =>
{
Assert.Equal (e.SuperView.Frame.Width, winAddedToTop.Frame.Width);
Assert.Equal (e.SuperView.Frame.Height, winAddedToTop.Frame.Height);
};
v1AddedToWin.Added += (s, e) =>
{
Assert.Equal (e.SuperView.Frame.Width, v1AddedToWin.Frame.Width);
Assert.Equal (e.SuperView.Frame.Height, v1AddedToWin.Frame.Height);
};
v2AddedToWin.Added += (s, e) =>
{
Assert.Equal (e.SuperView.Frame.Width, v2AddedToWin.Frame.Width);
Assert.Equal (e.SuperView.Frame.Height, v2AddedToWin.Frame.Height);
};
svAddedTov1.Added += (s, e) =>
{
Assert.Equal (e.SuperView.Frame.Width, svAddedTov1.Frame.Width);
Assert.Equal (e.SuperView.Frame.Height, svAddedTov1.Frame.Height);
};
top.Initialized += (s, e) =>
{
tc++;
Assert.Equal (1, tc);
Assert.Equal (1, wc);
Assert.Equal (1, v1c);
Assert.Equal (1, v2c);
Assert.Equal (1, sv1c);
Assert.True (top.CanFocus);
Assert.True (winAddedToTop.CanFocus);
Assert.False (v1AddedToWin.CanFocus);
Assert.False (v2AddedToWin.CanFocus);
Assert.False (svAddedTov1.CanFocus);
Application.LayoutAndDraw ();
};
winAddedToTop.Initialized += (s, e) =>
{
wc++;
Assert.Equal (top.Viewport.Width, winAddedToTop.Frame.Width);
Assert.Equal (top.Viewport.Height, winAddedToTop.Frame.Height);
};
v1AddedToWin.Initialized += (s, e) =>
{
v1c++;
// Top.Frame: 0, 0, 80, 25; Top.Viewport: 0, 0, 80, 25
// BUGBUG: This is wrong, it should be 78, 23. This test has always been broken.
// in no way should the v1AddedToWin.Frame be the same as the Top.Frame/Viewport
// as it is a subview of winAddedToTop, which has a border!
//Assert.Equal (top.Viewport.Width, v1AddedToWin.Frame.Width);
//Assert.Equal (top.Viewport.Height, v1AddedToWin.Frame.Height);
};
v2AddedToWin.Initialized += (s, e) =>
{
v2c++;
// Top.Frame: 0, 0, 80, 25; Top.Viewport: 0, 0, 80, 25
// BUGBUG: This is wrong, it should be 78, 23. This test has always been broken.
// in no way should the v2AddedToWin.Frame be the same as the Top.Frame/Viewport
// as it is a subview of winAddedToTop, which has a border!
//Assert.Equal (top.Viewport.Width, v2AddedToWin.Frame.Width);
//Assert.Equal (top.Viewport.Height, v2AddedToWin.Frame.Height);
};
svAddedTov1.Initialized += (s, e) =>
{
sv1c++;
// Top.Frame: 0, 0, 80, 25; Top.Viewport: 0, 0, 80, 25
// BUGBUG: This is wrong, it should be 78, 23. This test has always been broken.
// in no way should the svAddedTov1.Frame be the same as the Top.Frame/Viewport
// because sv1AddedTov1 is a subview of v1AddedToWin, which is a subview of
// winAddedToTop, which has a border!
//Assert.Equal (top.Viewport.Width, svAddedTov1.Frame.Width);
//Assert.Equal (top.Viewport.Height, svAddedTov1.Frame.Height);
Assert.False (svAddedTov1.CanFocus);
//Assert.Throws<InvalidOperationException> (() => svAddedTov1.CanFocus = true);
Assert.False (svAddedTov1.CanFocus);
};
v1AddedToWin.Add (svAddedTov1);
winAddedToTop.Add (v1AddedToWin, v2AddedToWin);
top.Add (winAddedToTop);
Application.Iteration += (s, a) =>
{
Application.LayoutAndDraw ();
top.Running = false;
};
Application.Run (top);
top.Dispose ();
Application.Shutdown ();
Assert.Equal (1, tc);
Assert.Equal (1, wc);
Assert.Equal (1, v1c);
Assert.Equal (1, v2c);
Assert.Equal (1, sv1c);
Assert.True (top.CanFocus);
Assert.True (winAddedToTop.CanFocus);
Assert.False (v1AddedToWin.CanFocus);
Assert.False (v2AddedToWin.CanFocus);
Assert.False (svAddedTov1.CanFocus);
v1AddedToWin.CanFocus = true;
Assert.False (svAddedTov1.CanFocus); // False because sv1 was disposed and it isn't a subview of v1.
}
[Fact]
[TestRespondersDisposed]
public void Initialized_Event_Will_Be_Invoked_When_Added_Dynamically ()
{
Application.Init (new FakeDriver ());
var t = new Toplevel { Id = "0" };
var w = new Window { Id = "t", Width = Dim.Fill (), Height = Dim.Fill () };
var v1 = new View { Id = "v1", Width = Dim.Fill (), Height = Dim.Fill () };
var v2 = new View { Id = "v2", Width = Dim.Fill (), Height = Dim.Fill () };
int tc = 0, wc = 0, v1c = 0, v2c = 0, sv1c = 0;
t.Initialized += (s, e) =>
{
tc++;
Assert.Equal (1, tc);
Assert.Equal (1, wc);
Assert.Equal (1, v1c);
Assert.Equal (1, v2c);
Assert.Equal (0, sv1c); // Added after t in the Application.Iteration.
Assert.True (t.CanFocus);
Assert.True (w.CanFocus);
Assert.False (v1.CanFocus);
Assert.False (v2.CanFocus);
Application.LayoutAndDraw ();
};
w.Initialized += (s, e) =>
{
wc++;
Assert.Equal (t.Viewport.Width, w.Frame.Width);
Assert.Equal (t.Viewport.Height, w.Frame.Height);
};
v1.Initialized += (s, e) =>
{
v1c++;
//Assert.Equal (t.Viewport.Width, v1.Frame.Width);
//Assert.Equal (t.Viewport.Height, v1.Frame.Height);
};
v2.Initialized += (s, e) =>
{
v2c++;
//Assert.Equal (t.Viewport.Width, v2.Frame.Width);
//Assert.Equal (t.Viewport.Height, v2.Frame.Height);
};
w.Add (v1, v2);
t.Add (w);
Application.Iteration += (s, a) =>
{
var sv1 = new View { Id = "sv1", Width = Dim.Fill (), Height = Dim.Fill () };
sv1.Initialized += (s, e) =>
{
sv1c++;
Assert.NotEqual (t.Frame.Width, sv1.Frame.Width);
Assert.NotEqual (t.Frame.Height, sv1.Frame.Height);
Assert.False (sv1.CanFocus);
//Assert.Throws<InvalidOperationException> (() => sv1.CanFocus = true);
Assert.False (sv1.CanFocus);
};
v1.Add (sv1);
Application.LayoutAndDraw ();
t.Running = false;
};
Application.Run (t);
t.Dispose ();
Application.Shutdown ();
Assert.Equal (1, tc);
Assert.Equal (1, wc);
Assert.Equal (1, v1c);
Assert.Equal (1, v2c);
Assert.Equal (1, sv1c);
Assert.True (t.CanFocus);
Assert.True (w.CanFocus);
Assert.False (v1.CanFocus);
Assert.False (v2.CanFocus);
}}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,446 @@
namespace Terminal.Gui.ViewTests;
public class ViewCommandTests
{
#region OnAccept/Accept tests
[Fact]
public void Accept_Command_Raises_NoFocus ()
{
var view = new ViewEventTester ();
Assert.False (view.HasFocus);
Assert.False (view.InvokeCommand (Command.Accept)); // there's no superview, so it should return true?
Assert.Equal (1, view.OnAcceptedCount);
Assert.Equal (1, view.AcceptedCount);
Assert.False (view.HasFocus);
}
[Fact]
public void Accept_Command_Handle_OnAccept_NoEvent ()
{
var view = new ViewEventTester ();
Assert.False (view.HasFocus);
view.HandleOnAccepted = true;
Assert.True (view.InvokeCommand (Command.Accept));
Assert.Equal (1, view.OnAcceptedCount);
Assert.Equal (0, view.AcceptedCount);
}
[Fact]
public void Accept_Handle_Event_OnAccept_Returns_True ()
{
var view = new View ();
var acceptInvoked = false;
view.Accepting += ViewOnAccept;
bool? ret = view.InvokeCommand (Command.Accept);
Assert.True (ret);
Assert.True (acceptInvoked);
return;
void ViewOnAccept (object sender, CommandEventArgs e)
{
acceptInvoked = true;
e.Cancel = true;
}
}
[Fact]
public void Accept_Command_Invokes_Accept_Event ()
{
var view = new View ();
var accepted = false;
view.Accepting += ViewOnAccept;
view.InvokeCommand (Command.Accept);
Assert.True (accepted);
return;
void ViewOnAccept (object sender, CommandEventArgs e) { accepted = true; }
}
// Accept on subview should bubble up to parent
[Fact]
public void Accept_Command_Bubbles_Up_To_SuperView ()
{
var view = new ViewEventTester { Id = "view" };
var subview = new ViewEventTester { Id = "subview" };
view.Add (subview);
subview.InvokeCommand (Command.Accept);
Assert.Equal (1, subview.OnAcceptedCount);
Assert.Equal (1, view.OnAcceptedCount);
subview.HandleOnAccepted = true;
subview.InvokeCommand (Command.Accept);
Assert.Equal (2, subview.OnAcceptedCount);
Assert.Equal (1, view.OnAcceptedCount);
subview.HandleOnAccepted = false;
subview.HandleAccepted = true;
subview.InvokeCommand (Command.Accept);
Assert.Equal (3, subview.OnAcceptedCount);
Assert.Equal (1, view.OnAcceptedCount);
// Add a super view to test deeper hierarchy
var superView = new ViewEventTester { Id = "superView" };
superView.Add (view);
subview.InvokeCommand (Command.Accept);
Assert.Equal (4, subview.OnAcceptedCount);
Assert.Equal (1, view.OnAcceptedCount);
Assert.Equal (0, superView.OnAcceptedCount);
subview.HandleAccepted = false;
subview.InvokeCommand (Command.Accept);
Assert.Equal (5, subview.OnAcceptedCount);
Assert.Equal (2, view.OnAcceptedCount);
Assert.Equal (1, superView.OnAcceptedCount);
view.HandleAccepted = true;
subview.InvokeCommand (Command.Accept);
Assert.Equal (6, subview.OnAcceptedCount);
Assert.Equal (3, view.OnAcceptedCount);
Assert.Equal (1, superView.OnAcceptedCount);
}
[Fact]
public void MouseClick_Does_Not_Invoke_Accept_Command ()
{
var view = new ViewEventTester ();
view.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked, Position = Point.Empty, View = view });
Assert.Equal (0, view.OnAcceptedCount);
}
// See https://github.com/gui-cs/Terminal.Gui/issues/3913
[Fact]
public void Button_IsDefault_Raises_Accepted_Correctly ()
{
int A_AcceptedCount = 0;
bool A_CancelAccepting = false;
int B_AcceptedCount = 0;
bool B_CancelAccepting = false;
var w = new Window ()
{
BorderStyle = LineStyle.None,
Width = 10,
Height = 10
};
var btnA = new Button ()
{
Width = 3,
IsDefault = true
};
btnA.Accepting += (s, e) =>
{
A_AcceptedCount++;
e.Cancel = A_CancelAccepting;
};
var btnB = new Button ()
{
Width = 3,
X = Pos.Right (btnA)
};
btnB.Accepting += (s, e) =>
{
B_AcceptedCount++;
e.Cancel = B_CancelAccepting;
};
w.Add (btnA, btnB);
w.LayoutSubviews ();
Application.Top = w;
Application.TopLevels.Push(w);
Assert.Same (Application.Top, w);
// Click button 2
var btn2Frame = btnB.FrameToScreen ();
Application.RaiseMouseEvent (
new MouseEventArgs ()
{
ScreenPosition = btn2Frame.Location,
Flags = MouseFlags.Button1Clicked
});
// Button A should have been accepted because B didn't cancel and A IsDefault
Assert.Equal (1, A_AcceptedCount);
Assert.Equal (1, B_AcceptedCount);
B_CancelAccepting = true;
Application.RaiseMouseEvent (
new MouseEventArgs ()
{
ScreenPosition = btn2Frame.Location,
Flags = MouseFlags.Button1Clicked
});
// Button A (IsDefault) should NOT have been accepted because B canceled
Assert.Equal (1, A_AcceptedCount);
Assert.Equal (2, B_AcceptedCount);
Application.ResetState (true);
}
// See: https://github.com/gui-cs/Terminal.Gui/issues/3905
[Fact]
public void Button_CanFocus_False_Raises_Accepted_Correctly ()
{
int wAcceptedCount = 0;
bool wCancelAccepting = false;
var w = new Window ()
{
Title = "Window",
BorderStyle = LineStyle.None,
Width = 10,
Height = 10
};
w.Accepting += (s, e) =>
{
wAcceptedCount++;
e.Cancel = wCancelAccepting;
};
int btnAcceptedCount = 0;
bool btnCancelAccepting = false;
var btn = new Button ()
{
Title = "Button",
Width = 3,
IsDefault = true,
};
btn.CanFocus = true;
btn.Accepting += (s, e) =>
{
btnAcceptedCount++;
e.Cancel = btnCancelAccepting;
};
w.Add (btn);
Application.Top = w;
Application.TopLevels.Push (w);
Assert.Same (Application.Top, w);
w.LayoutSubviews ();
// Click button just like a driver would
var btnFrame = btn.FrameToScreen ();
Application.RaiseMouseEvent (
new MouseEventArgs ()
{
ScreenPosition = btnFrame.Location,
Flags = MouseFlags.Button1Pressed
});
Application.RaiseMouseEvent (
new MouseEventArgs ()
{
ScreenPosition = btnFrame.Location,
Flags = MouseFlags.Button1Released
});
Application.RaiseMouseEvent (
new MouseEventArgs ()
{
ScreenPosition = btnFrame.Location,
Flags = MouseFlags.Button1Clicked
});
Assert.Equal (1, btnAcceptedCount);
Assert.Equal (2, wAcceptedCount);
Application.ResetState (true);
}
#endregion OnAccept/Accept tests
#region OnSelect/Select tests
[Theory]
[CombinatorialData]
public void Select_Command_Raises_SetsFocus (bool canFocus)
{
var view = new ViewEventTester
{
CanFocus = canFocus
};
Assert.Equal (canFocus, view.CanFocus);
Assert.False (view.HasFocus);
view.InvokeCommand (Command.Select);
Assert.Equal (1, view.OnSelectingCount);
Assert.Equal (1, view.SelectingCount);
Assert.Equal (canFocus, view.HasFocus);
}
[Fact]
public void Select_Command_Handle_OnSelecting_NoEvent ()
{
var view = new ViewEventTester ();
Assert.False (view.HasFocus);
view.HandleOnSelecting = true;
Assert.True (view.InvokeCommand (Command.Select));
Assert.Equal (1, view.OnSelectingCount);
Assert.Equal (0, view.SelectingCount);
}
[Fact]
public void Select_Handle_Event_OnSelecting_Returns_True ()
{
var view = new View ();
var SelectingInvoked = false;
view.Selecting += ViewOnSelect;
bool? ret = view.InvokeCommand (Command.Select);
Assert.True (ret);
Assert.True (SelectingInvoked);
return;
void ViewOnSelect (object sender, CommandEventArgs e)
{
SelectingInvoked = true;
e.Cancel = true;
}
}
[Fact]
public void Select_Command_Invokes_Selecting_Event ()
{
var view = new View ();
var selecting = false;
view.Selecting += ViewOnSelecting;
view.InvokeCommand (Command.Select);
Assert.True (selecting);
return;
void ViewOnSelecting (object sender, CommandEventArgs e) { selecting = true; }
}
[Fact]
public void MouseClick_Invokes_Select_Command ()
{
var view = new ViewEventTester ();
view.NewMouseEvent (new () { Flags = MouseFlags.Button1Clicked, Position = Point.Empty, View = view });
Assert.Equal (1, view.OnSelectingCount);
}
#endregion OnSelect/Select tests
#region OnHotKey/HotKey tests
[Fact]
public void HotKey_Command_SetsFocus ()
{
var view = new View ();
view.CanFocus = true;
Assert.False (view.HasFocus);
view.InvokeCommand (Command.HotKey);
Assert.True (view.HasFocus);
}
#endregion OnHotKey/HotKey tests
public class ViewEventTester : View
{
public ViewEventTester ()
{
CanFocus = true;
Accepting += (s, a) =>
{
a.Cancel = HandleAccepted;
AcceptedCount++;
};
HandlingHotKey += (s, a) =>
{
a.Cancel = HandleHandlingHotKey;
HandlingHotKeyCount++;
};
Selecting += (s, a) =>
{
a.Cancel = HandleSelecting;
SelectingCount++;
};
}
public int OnAcceptedCount { get; set; }
public int AcceptedCount { get; set; }
public bool HandleOnAccepted { get; set; }
/// <inheritdoc/>
protected override bool OnAccepting (CommandEventArgs args)
{
OnAcceptedCount++;
return HandleOnAccepted;
}
public bool HandleAccepted { get; set; }
public int OnHandlingHotKeyCount { get; set; }
public int HandlingHotKeyCount { get; set; }
public bool HandleOnHandlingHotKey { get; set; }
/// <inheritdoc/>
protected override bool OnHandlingHotKey (CommandEventArgs args)
{
OnHandlingHotKeyCount++;
return HandleOnHandlingHotKey;
}
public bool HandleHandlingHotKey { get; set; }
public int OnSelectingCount { get; set; }
public int SelectingCount { get; set; }
public bool HandleOnSelecting { get; set; }
/// <inheritdoc/>
protected override bool OnSelecting (CommandEventArgs args)
{
OnSelectingCount++;
return HandleOnSelecting;
}
public bool HandleSelecting { get; set; }
}
}

View File

@@ -0,0 +1,539 @@
using System.ComponentModel;
using System.Text;
using UnitTests;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.ViewTests;
public class ViewTests
{
private ITestOutputHelper _output;
public ViewTests (ITestOutputHelper output)
{
output = output;
#if DEBUG_IDISPOSABLE
View.DebugIDisposable = true;
#endif
}
// Generic lifetime (IDisposable) tests
[Fact]
[TestRespondersDisposed]
public void Dispose_Works ()
{
var r = new View ();
#if DEBUG_IDISPOSABLE
Assert.Equal (4, View.Instances.Count);
#endif
r.Dispose ();
#if DEBUG_IDISPOSABLE
Assert.Empty (View.Instances);
#endif
}
[Fact]
public void Disposing_Event_Notify_All_Subscribers_On_The_First_Container ()
{
#if DEBUG_IDISPOSABLE
// Only clear before because need to test after assert
View.Instances.Clear ();
#endif
var container1 = new View { Id = "Container1" };
var count = 0;
var view = new View { Id = "View" };
view.Disposing += View_Disposing;
container1.Add (view);
Assert.Equal (container1, view.SuperView);
void View_Disposing (object sender, EventArgs e)
{
count++;
Assert.Equal (view, sender);
container1.Remove ((View)sender);
}
Assert.Single (container1.Subviews);
var container2 = new View { Id = "Container2" };
container2.Add (view);
Assert.Equal (container2, view.SuperView);
Assert.Equal (container1.Subviews.Count, container2.Subviews.Count);
container2.Dispose ();
Assert.Empty (container1.Subviews);
Assert.Empty (container2.Subviews);
Assert.Equal (1, count);
Assert.Null (view.SuperView);
container1.Dispose ();
#if DEBUG_IDISPOSABLE
Assert.Empty (View.Instances);
#endif
}
[Fact]
public void Disposing_Event_Notify_All_Subscribers_On_The_Second_Container ()
{
#if DEBUG_IDISPOSABLE
// Only clear before because need to test after assert
View.Instances.Clear ();
#endif
var container1 = new View { Id = "Container1" };
var view = new View { Id = "View" };
container1.Add (view);
Assert.Equal (container1, view.SuperView);
Assert.Single (container1.Subviews);
var container2 = new View { Id = "Container2" };
var count = 0;
view.Disposing += View_Disposing;
container2.Add (view);
Assert.Equal (container2, view.SuperView);
void View_Disposing (object sender, EventArgs e)
{
count++;
Assert.Equal (view, sender);
container2.Remove ((View)sender);
}
Assert.Equal (container1.Subviews.Count, container2.Subviews.Count);
container1.Dispose ();
Assert.Empty (container1.Subviews);
Assert.Empty (container2.Subviews);
Assert.Equal (1, count);
Assert.Null (view.SuperView);
container2.Dispose ();
#if DEBUG_IDISPOSABLE
Assert.Empty (View.Instances);
#endif
}
[Fact]
public void Not_Notifying_Dispose ()
{
// Only clear before because need to test after assert
#if DEBUG_IDISPOSABLE
View.Instances.Clear ();
#endif
var container1 = new View { Id = "Container1" };
var view = new View { Id = "View" };
container1.Add (view);
Assert.Equal (container1, view.SuperView);
Assert.Single (container1.Subviews);
var container2 = new View { Id = "Container2" };
container2.Add (view);
Assert.Equal (container2, view.SuperView);
Assert.Equal (container1.Subviews.Count, container2.Subviews.Count);
container1.Dispose ();
Assert.Empty (container1.Subviews);
Assert.NotEmpty (container2.Subviews);
Assert.Single (container2.Subviews);
Assert.Null (view.SuperView);
// Trying access disposed properties
#if DEBUG_IDISPOSABLE
Assert.True (container2.Subviews [0].WasDisposed);
#endif
Assert.False (container2.Subviews [0].CanFocus);
Assert.Null (container2.Subviews [0].Margin);
Assert.Null (container2.Subviews [0].Border);
Assert.Null (container2.Subviews [0].Padding);
Assert.Null (view.SuperView);
container2.Dispose ();
#if DEBUG_IDISPOSABLE
Assert.Empty (View.Instances);
#endif
}
[Fact]
[TestRespondersDisposed]
public void Dispose_View ()
{
var view = new View ();
Assert.NotNull (view.Margin);
Assert.NotNull (view.Border);
Assert.NotNull (view.Padding);
#if DEBUG_IDISPOSABLE
Assert.Equal (4, View.Instances.Count);
#endif
view.Dispose ();
Assert.Null (view.Margin);
Assert.Null (view.Border);
Assert.Null (view.Padding);
}
[Fact]
public void Internal_Tests ()
{
var rect = new Rectangle (1, 1, 10, 1);
var view = new View { Frame = rect };
}
[Fact]
[TestRespondersDisposed]
public void New_Initializes ()
{
// Parameterless
var r = new View ();
Assert.NotNull (r);
Assert.True (r.Enabled);
Assert.True (r.Visible);
Assert.Equal ($"View(){r.Viewport}", r.ToString ());
Assert.False (r.CanFocus);
Assert.False (r.HasFocus);
Assert.Equal (new (0, 0, 0, 0), r.Viewport);
Assert.Equal (new (0, 0, 0, 0), r.Frame);
Assert.Null (r.Focused);
Assert.Null (r.ColorScheme);
Assert.Equal (0, r.Width);
Assert.Equal (0, r.Height);
Assert.Equal (0, r.X);
Assert.Equal (0, r.Y);
Assert.False (r.IsCurrentTop);
Assert.Empty (r.Id);
Assert.Empty (r.Subviews);
Assert.False (r.WantContinuousButtonPressed);
Assert.False (r.WantMousePositionReports);
Assert.Null (r.SuperView);
Assert.Null (r.MostFocused);
Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
r.Dispose ();
// Empty Rect
r = new () { Frame = Rectangle.Empty };
Assert.NotNull (r);
Assert.Equal ($"View(){r.Viewport}", r.ToString ());
Assert.False (r.CanFocus);
Assert.False (r.HasFocus);
Assert.Equal (new (0, 0, 0, 0), r.Viewport);
Assert.Equal (new (0, 0, 0, 0), r.Frame);
Assert.Null (r.Focused);
Assert.Null (r.ColorScheme);
Assert.Equal (0, r.Width);
Assert.Equal (0, r.Height);
Assert.Equal (0, r.X);
Assert.Equal (0, r.Y);
Assert.False (r.IsCurrentTop);
Assert.Empty (r.Id);
Assert.Empty (r.Subviews);
Assert.False (r.WantContinuousButtonPressed);
Assert.False (r.WantMousePositionReports);
Assert.Null (r.SuperView);
Assert.Null (r.MostFocused);
Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
r.Dispose ();
// Rect with values
r = new () { Frame = new (1, 2, 3, 4) };
Assert.NotNull (r);
Assert.Equal ($"View(){r.Frame}", r.ToString ());
Assert.False (r.CanFocus);
Assert.False (r.HasFocus);
Assert.Equal (new (0, 0, 3, 4), r.Viewport);
Assert.Equal (new (1, 2, 3, 4), r.Frame);
Assert.Null (r.Focused);
Assert.Null (r.ColorScheme);
Assert.Equal (3, r.Width);
Assert.Equal (4, r.Height);
Assert.Equal (1, r.X);
Assert.Equal (2, r.Y);
Assert.False (r.IsCurrentTop);
Assert.Empty (r.Id);
Assert.Empty (r.Subviews);
Assert.False (r.WantContinuousButtonPressed);
Assert.False (r.WantMousePositionReports);
Assert.Null (r.SuperView);
Assert.Null (r.MostFocused);
Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
r.Dispose ();
// Initializes a view with a vertical direction
r = new ()
{
Text = "Vertical View",
TextDirection = TextDirection.TopBottom_LeftRight,
Width = Dim.Auto (),
Height = Dim.Auto ()
};
r.TextFormatter.WordWrap = false;
Assert.NotNull (r);
r.BeginInit ();
r.EndInit ();
Assert.False (r.CanFocus);
Assert.False (r.HasFocus);
Assert.Equal (new (0, 0, 1, 13), r.Viewport);
Assert.Equal (new (0, 0, 1, 13), r.Frame);
Assert.Null (r.Focused);
Assert.Null (r.ColorScheme);
Assert.False (r.IsCurrentTop);
#if DEBUG
Assert.Equal ("Vertical View", r.Id);
#else
Assert.Equal (string.Empty, r.Id);
#endif
Assert.Empty (r.Subviews);
Assert.False (r.WantContinuousButtonPressed);
Assert.False (r.WantMousePositionReports);
Assert.Null (r.SuperView);
Assert.Null (r.MostFocused);
Assert.Equal (TextDirection.TopBottom_LeftRight, r.TextDirection);
r.Dispose ();
}
[Fact]
[TestRespondersDisposed]
public void New_Methods_Return_False ()
{
var r = new View ();
Assert.False (r.NewKeyDownEvent (Key.Empty));
//Assert.False (r.OnKeyDown (new KeyEventArgs () { Key = Key.Unknown }));
Assert.False (r.NewKeyUpEvent (Key.Empty));
Assert.False (r.NewMouseEvent (new () { Flags = MouseFlags.AllEvents }));
r.Dispose ();
// TODO: Add more
}
[Fact]
[AutoInitShutdown]
public void Test_Nested_Views_With_Height_Equal_To_One ()
{
var v = new View { Width = 11, Height = 3, ColorScheme = new () };
var top = new View { Width = Dim.Fill (), Height = 1 };
var bottom = new View { Width = Dim.Fill (), Height = 1, Y = 2 };
top.Add (new Label { Text = "111" });
v.Add (top);
v.Add (new LineView (Orientation.Horizontal) { Y = 1 });
bottom.Add (new Label { Text = "222" });
v.Add (bottom);
v.BeginInit ();
v.EndInit ();
v.LayoutSubviews ();
v.Draw ();
var looksLike =
@"
111
───────────
222";
DriverAssert.AssertDriverContentsAre (looksLike, _output);
v.Dispose ();
top.Dispose ();
bottom.Dispose ();
}
[Fact]
[TestRespondersDisposed]
public void View_With_No_Difference_Between_An_Object_Initializer_Compute_And_A_Absolute ()
{
// Object Initializer Computed
var view = new View { X = 1, Y = 2, Width = 3, Height = 4 };
// Object Initializer Absolute
var super = new View { Frame = new (0, 0, 10, 10) };
super.Add (view);
super.BeginInit ();
super.EndInit ();
super.LayoutSubviews ();
Assert.Equal (1, view.X);
Assert.Equal (2, view.Y);
Assert.Equal (3, view.Width);
Assert.Equal (4, view.Height);
Assert.False (view.Frame.IsEmpty);
Assert.Equal (new (1, 2, 3, 4), view.Frame);
Assert.False (view.Viewport.IsEmpty);
Assert.Equal (new (0, 0, 3, 4), view.Viewport);
view.LayoutSubviews ();
Assert.Equal (1, view.X);
Assert.Equal (2, view.Y);
Assert.Equal (3, view.Width);
Assert.Equal (4, view.Height);
Assert.False (view.Frame.IsEmpty);
Assert.False (view.Viewport.IsEmpty);
super.Dispose ();
#if DEBUG_IDISPOSABLE
Assert.Empty (View.Instances);
#endif
// Default Constructor
view = new ();
Assert.Equal (0, view.X);
Assert.Equal (0, view.Y);
Assert.Equal (0, view.Width);
Assert.Equal (0, view.Height);
Assert.True (view.Frame.IsEmpty);
Assert.True (view.Viewport.IsEmpty);
view.Dispose ();
// Object Initializer
view = new () { X = 1, Y = 2, Text = "" };
Assert.Equal (1, view.X);
Assert.Equal (2, view.Y);
Assert.Equal (0, view.Width);
Assert.Equal (0, view.Height);
Assert.False (view.Frame.IsEmpty);
Assert.True (view.Viewport.IsEmpty);
view.Dispose ();
// Default Constructor and post assignment equivalent to Object Initializer
view = new ();
view.X = 1;
view.Y = 2;
view.Width = 3;
view.Height = 4;
super = new () { Frame = new (0, 0, 10, 10) };
super.Add (view);
super.BeginInit ();
super.EndInit ();
super.LayoutSubviews ();
Assert.Equal (1, view.X);
Assert.Equal (2, view.Y);
Assert.Equal (3, view.Width);
Assert.Equal (4, view.Height);
Assert.False (view.Frame.IsEmpty);
Assert.Equal (new (1, 2, 3, 4), view.Frame);
Assert.False (view.Viewport.IsEmpty);
Assert.Equal (new (0, 0, 3, 4), view.Viewport);
super.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Visible_Clear_The_View_Output ()
{
var view = new View { Text = "Testing visibility." }; // use View, not Label to avoid AutoSize == true
Assert.Equal (0, view.Frame.Width);
Assert.Equal (0, view.Height);
var win = new Window ();
win.Add (view);
Toplevel top = new ();
top.Add (win);
RunState rs = Application.Begin (top);
view.Width = Dim.Auto ();
view.Height = Dim.Auto ();
Application.RunIteration (ref rs);
Assert.Equal ("Testing visibility.".Length, view.Frame.Width);
Assert.True (view.Visible);
((FakeDriver)Application.Driver!).SetBufferSize (30, 5);
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌────────────────────────────┐
│Testing visibility. │
│ │
│ │
└────────────────────────────┘
",
_output
);
view.Visible = false;
var firstIteration = false;
Application.RunIteration (ref rs, firstIteration);
DriverAssert.AssertDriverContentsWithFrameAre (
@"
┌────────────────────────────┐
│ │
│ │
│ │
└────────────────────────────┘
",
_output
);
Application.End (rs);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Visible_Sets_Also_Sets_Subviews ()
{
var button = new Button { Text = "Click Me" };
var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () };
win.Add (button);
Toplevel top = new ();
top.Add (win);
var iterations = 0;
Application.Iteration += (s, a) =>
{
iterations++;
Assert.True (button.Visible);
Assert.True (button.CanFocus);
Assert.True (button.HasFocus);
Assert.True (win.Visible);
Assert.True (win.CanFocus);
Assert.True (win.HasFocus);
win.Visible = false;
Assert.True (button.Visible);
Assert.True (button.CanFocus);
Assert.False (button.HasFocus);
Assert.False (win.Visible);
Assert.True (win.CanFocus);
Assert.False (win.HasFocus);
button.SetFocus ();
Assert.False (button.HasFocus);
Assert.False (win.HasFocus);
win.SetFocus ();
Assert.False (button.HasFocus);
Assert.False (win.HasFocus);
win.Visible = true;
Assert.True (button.HasFocus);
Assert.True (win.HasFocus);
Application.RequestStop ();
};
Application.Run (top);
top.Dispose ();
Assert.Equal (1, iterations);
}
}