Files
Terminal.Gui/Tests/UnitTests/Dialogs/MessageBoxTests.cs
Thomas Nind 51dda7e69f Fixes #3947 Adds Fake driver and fixes fluent tests (iteration-zero) (#4225)
* Consider width2 chars that are not IsBmp

* Apply same fix in WindowsDriver

* Explicitly use type of local variable

* Revert changes to WindowsDriver

* Assume we are running in a terminal that supports true color by default unless user explicitly forces 16

* Switch to SetAttribute and WriteConsole instead of WriteConsoleOutput for 16 color mode

* Fix some cursor issues (WIP)

* Remove concept of 'dirty rows' from v2 as its never actually used

* Remove damageRegion as it does nothing

* Make string builder to console writing simpler

* Radically simplify Write method

* Simplify conditional logic

* Simplify restoring cursor position

* Reference local variable for console buffer

* Reduce calls to ConsoleWrite by accumulating till attribute changes

* When resizing v2 16 color mode on windows, recreate the back buffer to match its size

* Fixes for VTS enabled

* Fix _lastSize never being assigned

* Fixes VTS for Force16Colors

* Fixes force16Colors in VTS

* Fixes escape sequences always echoing in non-VTS

* Force Force16Colors in non-VTS. It have a bug in adding a newline in the last line

* WIP Add base class for NetOutput

* Abstract away how we change attribute

* WIP - Make WindowsOutput use base class

* WIP working to fix set cursor position

* Remove commented out code

* Fixes legacy output mode

* Fixes size with no alt buffer supported on VTS and size restore after maximized.

* Fix set cursor which also fixes the broken surrogate pairs

* Add force parameter

* Fixes an issue that only happens with Windows Terminal when paste surrogate pairs by press Ctrl+V

* In Windows escape sequences must be sent during the lifetime of the console which is created in input handle

* Ensure flush the input buffer before reset the console

* Flush input buffer before reset console in v2win

* Fixes issue in v2net not being refreshing the menu bar at start

* Only force layout and draw on size changed.

* Fix v2net issue not draw first line by forcing set cursor position

* Set _lastCursorPosition nullable and remove bool force from set cursor position

* Remove force parameter

* Add v2 version of fake driver attribute

* Make direct replacement and wire up window resizing events

* Update casts to use V2 fake driver instead

* Adjust interfaces to expose less internals

* Fix not raising iteration event in v2

* WIP investigate what it takes to do resize and redraw using TextAlignment_Centered as example

* Sketch adding component factory

* Create relevant fake component factories

* Add window size monitor into factory

* Fake size monitor injecting

* Add helper for faking console resize in AutoInitShutdown tests

* Fix size setting in FakeDriverV2

* Switch to new method

* Fix IsLegacy becoming false when using blank constructor

* Fix for Ready not being raised when showing same top twice also fixes garbage collection issue if running millions of top levels

* Fix tests

* Remove auto init

* Restore conditional compilation stuff

* Restore 'if running unit tests' logic

* Check only for the output being specific classes for the suppression

* Fix ShadowView blowing up with index out of bounds error

* Fix resize in fluent tests

* Fix for people using Iteration call directly

* Fix more calls to iteration to use
        AutoInitShutdownAttribute.RunIteration ();

* Add comment

* Remove assumption that Run with prior view not disposed should throw

* Fix timings in Dialog_Opened_From_Another_Dialog

* Fix Zero_Buttons_Works

* Standardize and fix Button_IsDefault_True_Return_His_Index_On_Accepting

* Fix iteration counts on MessageBoxTests

* Fix WizartTests and DrawTests_Ruler

* Implement SendKeys into ConsoleDriverFacade

* Fix SendKeys in console driver facade such that FileDialogTests works
Fix when Clip is null in popover

* Add missing dispose call to test

* Fix support for Esc in facade SendKeys

* Fix AutocompleteTests

* Fix various tests

* Replace LayoutAndDraw with run iteration

* Fix draw issues

* fix draw order

* Fix run iteration calls

* Fix unit tests

* Fix SendKeys in facade.

* Manipulate upper and lower cases.

* Add IsValidInput method to the interface.

* Fix SendKeys scenario

* Fixes surrogate pairs in the label

* Make tests more sensible - they are testing draw functionality.  Callbacks do not need to happen in Iteration method

* Fix tests and harden cleanup in AutoInitShutdownAttribute v2 lifecycle dispose

* Delete extra create input call

* Fix mocks and order of exceptions thrown in Run when things are not initialized

* Revert use of `MapConsoleKeyInfoToKeyCode`

* Ignore casing as it is not what test is really about

* Clear application top and top levels before each auto init shutdown test

* Fix for unstable tests

* Restore actually working SendKeys code

* option to pass logger in fluent ctor

* restore ToArray

* Fix SendKeys method and add extension to unit test

* Leverage the EscSeqUtils.MapConsoleKeyInfo method to avoid duplicate code

* Remove unnecessary hack

* Using only KeyCode for rKeys

* Recover modifier keys in surrogate pairs

* Reformat

* Remove iteration limit for benchmarking in v2

* remove iteration delay to identify bugs

* Remove nudge to unique key and make Then run on UI thread

* fix fluid assertions

* Ensure UI operations all happen on UI thread

* Add explicit error for WaitIteration during an invoke

* Remove timeout added for debug

* Catch failing asserts better

* Fix screenshot

* Fix null ref

* Fix race condition in processing input

* Test fixing

* Standardize asserts

* Remove calls to layout and draw, remove pointless lock and enable reading Cancelled from Dialog even if it is disposed

* fix bad merge

* Make logs access threadsafe

* add extra wait to remove race between iteration end and assert

* Code cleanup

* Remove test for crash on access Cancelled after dispose as this is no longer a restriction

* Change resize console to run on UI thread - fixing race condition with redrawing

* Restore original frame rate after test

* Restore nudge to unique key

* Code Cleanup

* Fix for cascading failures when an assert fails in a specific test

* fix for bad merge

* Address PR feedback

* Move classes to seperate files and add xmldoc

* xml doc warnings

* More xml comments docs

* Fix spelling

---------

Co-authored-by: BDisp <bd.bdisp@gmail.com>
2025-09-10 10:01:57 -06:00

517 lines
22 KiB
C#

using System.Text;
using UICatalog;
using UnitTests;
using Xunit.Abstractions;
namespace Terminal.Gui.DialogTests;
public class MessageBoxTests
{
private readonly ITestOutputHelper _output;
public MessageBoxTests (ITestOutputHelper output) { _output = output; }
[Fact]
[AutoInitShutdown]
public void KeyBindings_Enter_Causes_Focused_Button_Click_No_Accept ()
{
int result = -1;
var iteration = 0;
var btnAcceptCount = 0;
Application.Iteration += (s, a) =>
{
iteration++;
switch (iteration)
{
case 1:
result = MessageBox.Query (string.Empty, string.Empty, 0, false, "btn0", "btn1");
Application.RequestStop ();
break;
case 2:
// Tab to btn2
Application.RaiseKeyDownEvent (Key.Tab);
var btn = Application.Navigation!.GetFocused () as Button;
btn.Accepting += (sender, e) => { btnAcceptCount++; };
// Click
Application.RaiseKeyDownEvent (Key.Enter);
break;
default:
Assert.Fail ();
break;
}
};
Application.Run ().Dispose ();
Assert.Equal (1, result);
Assert.Equal (1, btnAcceptCount);
}
[Fact]
[AutoInitShutdown]
public void KeyBindings_Esc_Closes ()
{
var result = 999;
var iteration = 0;
Application.Iteration += (s, a) =>
{
iteration++;
switch (iteration)
{
case 1:
result = MessageBox.Query (string.Empty, string.Empty, 0, false, "btn0", "btn1");
Application.RequestStop ();
break;
case 2:
Application.RaiseKeyDownEvent (Key.Esc);
break;
default:
Assert.Fail ();
break;
}
};
Application.Run ().Dispose ();
Assert.Equal (-1, result);
}
[Fact]
[AutoInitShutdown]
public void KeyBindings_Space_Causes_Focused_Button_Click_No_Accept ()
{
int result = -1;
var iteration = 0;
var btnAcceptCount = 0;
Application.Iteration += (s, a) =>
{
iteration++;
switch (iteration)
{
case 1:
result = MessageBox.Query (string.Empty, string.Empty, 0, false, "btn0", "btn1");
Application.RequestStop ();
break;
case 2:
// Tab to btn2
Application.RaiseKeyDownEvent (Key.Tab);
var btn = Application.Navigation!.GetFocused () as Button;
btn.Accepting += (sender, e) => { btnAcceptCount++; };
Application.RaiseKeyDownEvent (Key.Space);
break;
default:
Assert.Fail ();
break;
}
};
Application.Run ().Dispose ();
Assert.Equal (1, result);
Assert.Equal (1, btnAcceptCount);
}
[Theory]
[InlineData (@"", false, false, 6, 6, 2, 2)]
[InlineData (@"", false, true, 3, 6, 9, 3)]
[InlineData (@"01234\n-----\n01234", false, false, 1, 6, 13, 3)]
[InlineData (@"01234\n-----\n01234", true, false, 1, 5, 13, 4)]
[InlineData (@"0123456789", false, false, 1, 6, 12, 3)]
[InlineData (@"0123456789", false, true, 1, 5, 12, 4)]
[InlineData (@"01234567890123456789", false, true, 1, 5, 13, 4)]
[InlineData (@"01234567890123456789", true, true, 1, 5, 13, 5)]
[InlineData (@"01234567890123456789\n01234567890123456789", false, true, 1, 5, 13, 4)]
[InlineData (@"01234567890123456789\n01234567890123456789", true, true, 1, 4, 13, 7)]
[AutoInitShutdown]
public void Location_And_Size_Correct (string message, bool wrapMessage, bool hasButton, int expectedX, int expectedY, int expectedW, int expectedH)
{
int iterations = -1;
AutoInitShutdownAttribute.FakeResize(new Size(15, 15)); // 15 x 15 gives us enough room for a button with one char (9x1)
Dialog.DefaultShadow = ShadowStyle.None;
Button.DefaultShadow = ShadowStyle.None;
var mbFrame = Rectangle.Empty;
Application.Iteration += (s, a) =>
{
iterations++;
if (iterations == 0)
{
MessageBox.Query (string.Empty, message, 0, wrapMessage, hasButton ? ["0"] : []);
Application.RequestStop ();
}
else if (iterations == 1)
{
mbFrame = Application.Top!.Frame;
Application.RequestStop ();
}
};
Application.Run ().Dispose ();
Assert.Equal (new (expectedX, expectedY, expectedW, expectedH), mbFrame);
}
[Fact]
[AutoInitShutdown]
public void Message_With_Spaces_WrapMessage_False ()
{
int iterations = -1;
var top = new Toplevel ();
top.BorderStyle = LineStyle.None;
AutoInitShutdownAttribute.FakeResize(new Size(20, 10));
var btn =
$"{Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} btn {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket}";
// Override CM
MessageBox.DefaultButtonAlignment = Alignment.End;
MessageBox.DefaultBorderStyle = LineStyle.Double;
Dialog.DefaultShadow = ShadowStyle.None;
Button.DefaultShadow = ShadowStyle.None;
Application.Iteration += (s, a) =>
{
iterations++;
if (iterations == 0)
{
var sb = new StringBuilder ();
for (var i = 0; i < 17; i++)
{
sb.Append ("ff ");
}
MessageBox.Query (string.Empty, sb.ToString (), 0, false, "btn");
Application.RequestStop ();
}
else if (iterations == 2)
{
DriverAssert.AssertDriverContentsWithFrameAre (
@"
╔════════════════╗
║ ff ff ff ff ff ║
║ ⟦► btn ◄⟧║
╚════════════════╝",
_output
);
Application.RequestStop ();
// Really long text
MessageBox.Query (string.Empty, new ('f', 500), 0, false, "btn");
}
else if (iterations == 4)
{
DriverAssert.AssertDriverContentsWithFrameAre (
@"
╔════════════════╗
║ffffffffffffffff║
║ ⟦► btn ◄⟧║
╚════════════════╝",
_output
);
Application.RequestStop ();
}
};
Application.Run (top);
top.Dispose ();
}
[Fact]
[AutoInitShutdown]
public void Message_With_Spaces_WrapMessage_True ()
{
int iterations = -1;
var top = new Toplevel ();
top.BorderStyle = LineStyle.None;
AutoInitShutdownAttribute.FakeResize(new Size(20, 10));
var btn =
$"{Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} btn {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket}";
// Override CM
MessageBox.DefaultButtonAlignment = Alignment.End;
MessageBox.DefaultBorderStyle = LineStyle.Double;
Dialog.DefaultShadow = ShadowStyle.None;
Button.DefaultShadow = ShadowStyle.None;
Application.Iteration += (s, a) =>
{
iterations++;
if (iterations == 0)
{
var sb = new StringBuilder ();
for (var i = 0; i < 17; i++)
{
sb.Append ("ff ");
}
MessageBox.Query (string.Empty, sb.ToString (), 0, true, "btn");
Application.RequestStop ();
}
else if (iterations == 2)
{
DriverAssert.AssertDriverContentsWithFrameAre (
@"
╔══════════════╗
║ff ff ff ff ff║
║ff ff ff ff ff║
║ff ff ff ff ff║
║ ff ff ║
║ ⟦► btn ◄⟧║
╚══════════════╝",
_output
);
Application.RequestStop ();
// Really long text
MessageBox.Query (string.Empty, new ('f', 500), 0, true, "btn");
}
else if (iterations == 4)
{
DriverAssert.AssertDriverContentsWithFrameAre (
@"
╔════════════════╗
║ffffffffffffffff║
║ffffffffffffffff║
║ffffffffffffffff║
║ffffffffffffffff║
║ffffffffffffffff║
║ffffffffffffffff║
║fffffff⟦► btn ◄⟧║
╚════════════════╝",
_output
);
Application.RequestStop ();
}
};
Application.Run (top);
top.Dispose ();
}
[Theory]
[InlineData (0, 0, "1")]
[InlineData (1, 1, "1")]
[InlineData (7, 5, "1")]
[InlineData (50, 50, "1")]
[InlineData (0, 0, "message")]
[InlineData (1, 1, "message")]
[InlineData (7, 5, "message")]
[InlineData (50, 50, "message")]
[AutoInitShutdown]
public void Size_Not_Default_Message (int height, int width, string message)
{
int iterations = -1;
AutoInitShutdownAttribute.FakeResize(new Size(100, 100));
Application.Iteration += (s, a) =>
{
iterations++;
if (iterations == 0)
{
MessageBox.Query (height, width, string.Empty, message, null);
Application.RequestStop ();
}
else if (iterations == 1)
{
AutoInitShutdownAttribute.RunIteration ();
Assert.IsType<Dialog> (Application.Top);
Assert.Equal (new (height, width), Application.Top.Frame.Size);
Application.RequestStop ();
}
};
}
[Theory]
[InlineData (0, 0, "1")]
[InlineData (1, 1, "1")]
[InlineData (7, 5, "1")]
[InlineData (50, 50, "1")]
[InlineData (0, 0, "message")]
[InlineData (1, 1, "message")]
[InlineData (7, 5, "message")]
[InlineData (50, 50, "message")]
[AutoInitShutdown]
public void Size_Not_Default_Message_Button (int height, int width, string message)
{
int iterations = -1;
AutoInitShutdownAttribute.FakeResize(new Size(100, 100));
Application.Iteration += (s, a) =>
{
iterations++;
if (iterations == 0)
{
MessageBox.Query (height, width, string.Empty, message, "_Ok");
Application.RequestStop ();
}
else if (iterations == 1)
{
AutoInitShutdownAttribute.RunIteration ();
Assert.IsType<Dialog> (Application.Top);
Assert.Equal (new (height, width), Application.Top.Frame.Size);
Application.RequestStop ();
}
};
}
[Theory]
[InlineData (0, 0)]
[InlineData (1, 1)]
[InlineData (7, 5)]
[InlineData (50, 50)]
[AutoInitShutdown]
public void Size_Not_Default_No_Message (int height, int width)
{
int iterations = -1;
AutoInitShutdownAttribute.FakeResize(new Size(100, 100));
Application.Iteration += (s, a) =>
{
iterations++;
if (iterations == 0)
{
MessageBox.Query (height, width, string.Empty, string.Empty, null);
Application.RequestStop ();
}
else if (iterations == 1)
{
AutoInitShutdownAttribute.RunIteration ();
Assert.IsType<Dialog> (Application.Top);
Assert.Equal (new (height, width), Application.Top.Frame.Size);
Application.RequestStop ();
}
};
}
[Fact]
[AutoInitShutdown]
public void UICatalog_AboutBox ()
{
int iterations = -1;
AutoInitShutdownAttribute.FakeResize (new Size (70, 15));
// Override CM
MessageBox.DefaultButtonAlignment = Alignment.End;
MessageBox.DefaultBorderStyle = LineStyle.Double;
Dialog.DefaultShadow = ShadowStyle.None;
Button.DefaultShadow = ShadowStyle.None;
Application.Iteration += (s, a) =>
{
iterations++;
if (iterations == 0)
{
MessageBox.Query (
"",
UICatalog.UICatalogTop.GetAboutBoxMessage (),
wrapMessage: false,
buttons: "_Ok"
);
Application.RequestStop ();
}
else if (iterations == 2)
{
var expectedText = """
UI Catalog: A comprehensive sample library and test app for
_______ _ _ _____ _
|__ __| (_) | | / ____| (_)
| | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _
| |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |
| | __/ | | | | | | | | | | | (_| | || |__| | |_| | |
|_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|
v2 - Pre-Alpha
Ok
""";
DriverAssert.AssertDriverContentsAre (expectedText, _output);
Application.RequestStop ();
}
};
var top = new Toplevel ();
top.BorderStyle = LineStyle.Single;
Application.Run (top);
top.Dispose ();
}
[Theory]
[MemberData (nameof (AcceptingKeys))]
[AutoInitShutdown]
public void Button_IsDefault_True_Return_His_Index_On_Accepting (Key key)
{
Application.Iteration += (_, _) => Assert.True (Application.RaiseKeyDownEvent (key));
int res = MessageBox.Query ("hey", "IsDefault", "Yes", "No");
Assert.Equal (0, res);
}
public static IEnumerable<object []> AcceptingKeys ()
{
yield return [Key.Enter];
yield return [Key.Space];
}
}