diff --git a/Terminal.Gui/Views/Menu/MenuBarv2.cs b/Terminal.Gui/Views/Menu/MenuBarv2.cs
index 7850d673c..4c62e42e2 100644
--- a/Terminal.Gui/Views/Menu/MenuBarv2.cs
+++ b/Terminal.Gui/Views/Menu/MenuBarv2.cs
@@ -327,7 +327,7 @@ public class MenuBarv2 : Menuv2, IDesignable
}
///
- public bool EnableForDesign (ref readonly TContext context) where TContext : notnull
+ public override bool EnableForDesign ()
{
Add (
new MenuBarItemv2 (
diff --git a/Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs b/Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs
new file mode 100644
index 000000000..81247bb94
--- /dev/null
+++ b/Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs
@@ -0,0 +1,297 @@
+using System.Reflection;
+using Terminal.Gui;
+using TerminalGuiFluentTesting;
+using Xunit.Abstractions;
+
+namespace IntegrationTests.FluentTests;
+
+///
+/// Tests for the MenuBarv2 class
+///
+public class MenuBarv2Tests
+{
+ private readonly TextWriter _out;
+
+ public MenuBarv2Tests (ITestOutputHelper outputHelper) { _out = new BasicFluentAssertionTests.TestOutputWriter (outputHelper); }
+
+ [Theory]
+ [ClassData (typeof (V2TestDrivers))]
+ public void Initializes_WithNoItems (V2TestDriver d)
+ {
+ using GuiTestContext c = With.A (80, 25, d)
+ .Then (
+ () =>
+ {
+ // Create a menu bar with no items
+ var menuBar = new MenuBarv2 ();
+ Assert.Equal (0, menuBar.SubViews.Count);
+ Assert.False (menuBar.CanFocus);
+ Assert.Equal (Orientation.Horizontal, menuBar.Orientation);
+ Assert.Equal (Key.F9, MenuBarv2.DefaultKey);
+ })
+ .WriteOutLogs (_out)
+ .Stop ();
+ }
+
+ [Theory]
+ [ClassData (typeof (V2TestDrivers))]
+ public void Initializes_WithItems (V2TestDriver d)
+ {
+ MenuBarItemv2 [] menuItems = [];
+
+ using GuiTestContext c = With.A (80, 25, d)
+ .Then (
+ () =>
+ {
+ // Create items for the menu bar
+ menuItems =
+ [
+ new (
+ "_File",
+ [
+ new MenuItemv2 ("_Open", "Opens a file", () => { })
+ ]),
+ new (
+ "_Edit",
+ [
+ new MenuItemv2 ("_Copy", "Copies selection", () => { })
+ ])
+ ];
+
+ var menuBar = new MenuBarv2 (menuItems);
+ Assert.Equal (2, menuBar.SubViews.Count);
+
+ // First item should be the File menu
+ var fileMenu = menuBar.SubViews.ElementAt (0) as MenuBarItemv2;
+ Assert.NotNull (fileMenu);
+ Assert.Equal ("_File", fileMenu.Title);
+
+ // Second item should be the Edit menu
+ var editMenu = menuBar.SubViews.ElementAt (1) as MenuBarItemv2;
+ Assert.NotNull (editMenu);
+ Assert.Equal ("_Edit", editMenu.Title);
+ })
+ .WriteOutLogs (_out)
+ .Stop ();
+ }
+
+ [Theory]
+ [ClassData (typeof (V2TestDrivers))]
+ public void AddsItems_WithMenusProperty (V2TestDriver d)
+ {
+ using GuiTestContext c = With.A (80, 25, d)
+ .Then (
+ () =>
+ {
+ var menuBar = new MenuBarv2 ();
+
+ // Set items through Menus property
+ menuBar.Menus =
+ [
+ new ("_File"),
+ new ("_Edit"),
+ new ("_View")
+ ];
+
+ Assert.Equal (3, menuBar.SubViews.Count);
+ })
+ .WriteOutLogs (_out)
+ .Stop ();
+ }
+
+ [Theory]
+ [ClassData (typeof (V2TestDrivers))]
+ public void ChangesKey_RaisesEvent (V2TestDriver d)
+ {
+ using GuiTestContext c = With.A (80, 25, d)
+ .Then (
+ () =>
+ {
+ var menuBar = new MenuBarv2 ();
+
+ var oldKeyValue = Key.Empty;
+ var newKeyValue = Key.Empty;
+ var eventRaised = false;
+
+ menuBar.KeyChanged += (_, args) =>
+ {
+ eventRaised = true;
+ oldKeyValue = args.OldKey;
+ newKeyValue = args.NewKey;
+ };
+
+ // Default key should be F9
+ Assert.Equal (Key.F9, menuBar.Key);
+
+ // Change key to F1
+ menuBar.Key = Key.F1;
+
+ // Verify event was raised
+ Assert.True (eventRaised);
+ Assert.Equal (Key.F9, oldKeyValue);
+ Assert.Equal (Key.F1, newKeyValue);
+
+ // Verify key was changed
+ Assert.Equal (Key.F1, menuBar.Key);
+ })
+ .WriteOutLogs (_out)
+ .Stop ();
+ }
+
+ [Theory]
+ [ClassData (typeof (V2TestDrivers))]
+ public void ShowHidePopovers (V2TestDriver d)
+ {
+ using GuiTestContext c = With.A (80, 25, d)
+ .Then (
+ () =>
+ {
+ // Create a menu bar with items that have submenus
+ var fileMenuItem = new MenuBarItemv2 (
+ "_File",
+ [
+ new MenuItemv2 ("_Open", string.Empty, null),
+ new MenuItemv2 ("_Save", string.Empty, null)
+ ]);
+
+ var menuBar = new MenuBarv2 ([fileMenuItem]);
+
+ // Initially, no menu should be open
+ Assert.False (menuBar.IsOpen ());
+ Assert.False (menuBar.IsActive ());
+
+ // Initialize the menu bar
+ menuBar.BeginInit ();
+ menuBar.EndInit ();
+
+ // Simulate showing a popover menu by manipulating the first menu item
+ MethodInfo? showPopoverMethod = typeof (MenuBarv2).GetMethod (
+ "ShowPopover",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+
+ // Set menu bar to active state using reflection
+ FieldInfo? activeField = typeof (MenuBarv2).GetField (
+ "_active",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ activeField?.SetValue (menuBar, true);
+ menuBar.CanFocus = true;
+
+ // Show the popover menu
+ showPopoverMethod?.Invoke (menuBar, new object? [] { fileMenuItem });
+
+ // Should be active now
+ Assert.True (menuBar.IsActive ());
+
+ // Test if we can hide the popover menu
+ fileMenuItem.PopoverMenu.Visible = true;
+
+ Assert.True (menuBar.HideActiveItem ());
+
+ // Menu should no longer be open or active
+ Assert.False (menuBar.IsActive ());
+ Assert.False (menuBar.IsOpen ());
+ Assert.False (menuBar.CanFocus);
+ })
+ .WriteOutLogs (_out)
+ .Stop ();
+ }
+
+ [Theory]
+ [ClassData (typeof (V2TestDrivers))]
+ public void EnableForDesign_CreatesMenuItems (V2TestDriver d)
+ {
+ using GuiTestContext c = With.A (80, 25, d)
+ .Then (
+ () =>
+ {
+ var menuBar = new MenuBarv2 ();
+ Application.Top.Add (menuBar);
+
+ // Call EnableForDesign
+ bool result = menuBar.EnableForDesign ();
+
+ // Should return true
+ Assert.True (result);
+
+ // Should have created menu items
+ Assert.True (menuBar.SubViews.Count > 0);
+
+ // Should have File, Edit and Help menus
+ View? fileMenu = menuBar.SubViews.FirstOrDefault (v => (v as MenuBarItemv2)?.Title == "_File");
+ View? editMenu = menuBar.SubViews.FirstOrDefault (v => (v as MenuBarItemv2)?.Title == "_Edit");
+ View? helpMenu = menuBar.SubViews.FirstOrDefault (v => (v as MenuBarItemv2)?.Title == "_Help");
+
+ Assert.NotNull (fileMenu);
+ Assert.NotNull (editMenu);
+ Assert.NotNull (helpMenu);
+ })
+ .ScreenShot ("MenuBarv2 EnableForDesign", _out)
+ .WriteOutLogs (_out)
+ .Stop ();
+ }
+
+ [Theory]
+ [ClassData (typeof (V2TestDrivers))]
+ public void Navigation_BetweenItems (V2TestDriver d)
+ {
+ var menuBarActivated = false;
+
+ using GuiTestContext c = With.A (80, 25, d)
+ .Then (
+ () =>
+ {
+ // Create menu items
+ var fileMenu = new MenuBarItemv2 (
+ "_File",
+ [
+ new MenuItemv2 ("_Open", string.Empty, null),
+ new MenuItemv2 ("_Save", string.Empty, null)
+ ]);
+
+ var editMenu = new MenuBarItemv2 (
+ "_Edit",
+ [
+ new MenuItemv2 ("_Cut", string.Empty, null),
+ new MenuItemv2 ("_Copy", string.Empty, null)
+ ]);
+
+ // Create menu bar and add to window
+ var menuBar = new MenuBarv2 ([fileMenu, editMenu]);
+ Application.Top.Add (menuBar);
+
+ // Set menu bar to active state using reflection
+ FieldInfo? activeField = typeof (MenuBarv2).GetField (
+ "_active",
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ activeField?.SetValue (menuBar, true);
+ menuBar.CanFocus = true;
+ menuBarActivated = true;
+
+ // Give focus to the first menu item
+ fileMenu.SetFocus ();
+ Assert.True (fileMenu.HasFocus);
+
+ Application.LayoutAndDraw ();
+ })
+ .ScreenShot ("MenuBar initial state", _out)
+ .Then (
+ () =>
+ {
+ if (!menuBarActivated)
+ {
+ // Skip further tests if activation failed
+ }
+
+ // Move right to select the edit menu
+ // This simulates navigation between menu items
+ })
+ .Right ()
+ .ScreenShot ("After right arrow", _out)
+ .Right ()
+ .ScreenShot ("After second right arrow (should wrap)", _out)
+ .Left ()
+ .ScreenShot ("After left arrow", _out)
+ .WriteOutLogs (_out)
+ .Stop ();
+ }
+}
diff --git a/Tests/UnitTests/Dialogs/MessageBoxTests.cs b/Tests/UnitTests/Dialogs/MessageBoxTests.cs
index 9216b863e..7777806a8 100644
--- a/Tests/UnitTests/Dialogs/MessageBoxTests.cs
+++ b/Tests/UnitTests/Dialogs/MessageBoxTests.cs
@@ -462,7 +462,7 @@ public class MessageBoxTests
{
MessageBox.Query (
"",
- UICatalog.UICatalogTopLevel.GetAboutBoxMessage (),
+ UICatalog.UICatalogTop.GetAboutBoxMessage (),
wrapMessage: false,
buttons: "_Ok"
);
diff --git a/Tests/UnitTests/Text/TextFormatterTests.cs b/Tests/UnitTests/Text/TextFormatterTests.cs
index fe6dc292a..d4367bdd3 100644
--- a/Tests/UnitTests/Text/TextFormatterTests.cs
+++ b/Tests/UnitTests/Text/TextFormatterTests.cs
@@ -4146,7 +4146,7 @@ Nice Work")]
{
TextFormatter tf = new ()
{
- Text = UICatalog.UICatalogTopLevel.GetAboutBoxMessage (),
+ Text = UICatalog.UICatalogTop.GetAboutBoxMessage (),
Alignment = Alignment.Center,
VerticalAlignment = Alignment.Start,
WordWrap = false,
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index f41f5e354..4006bd980 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -61,8 +61,8 @@ public class UICatalog
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
}
- UICatalogTopLevel.CachedScenarios = Scenario.GetScenarios ();
- UICatalogTopLevel.CachedCategories = Scenario.GetAllCategories ();
+ UICatalogTop.CachedScenarios = Scenario.GetScenarios ();
+ UICatalogTop.CachedCategories = Scenario.GetAllCategories ();
// Process command line args
@@ -108,7 +108,7 @@ public class UICatalog
"The name of the Scenario to run. If not provided, the UI Catalog UI will be shown.",
getDefaultValue: () => "none"
).FromAmong (
- UICatalogTopLevel.CachedScenarios.Select (s => s.GetName ())
+ UICatalogTop.CachedScenarios.Select (s => s.GetName ())
.Append ("none")
.ToArray ()
);
@@ -217,20 +217,20 @@ public class UICatalog
Application.Init (driverName: _forceDriver);
- if (string.IsNullOrWhiteSpace (UICatalogTopLevel.CachedTheme))
+ if (string.IsNullOrWhiteSpace (UICatalogTop.CachedTheme))
{
- UICatalogTopLevel.CachedTheme = Themes?.Theme;
+ UICatalogTop.CachedTheme = Themes?.Theme;
}
else
{
- Themes!.Theme = UICatalogTopLevel.CachedTheme;
+ Themes!.Theme = UICatalogTop.CachedTheme;
Apply ();
}
- Application.Run ().Dispose ();
+ Application.Run ().Dispose ();
Application.Shutdown ();
- return UICatalogTopLevel.CachedSelectedScenario!;
+ return UICatalogTop.CachedSelectedScenario!;
}
[SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "")]
@@ -323,15 +323,15 @@ public class UICatalog
// run it and exit when done.
if (options.Scenario != "none")
{
- int item = UICatalogTopLevel.CachedScenarios!.IndexOf (
- UICatalogTopLevel.CachedScenarios!.FirstOrDefault (
+ int item = UICatalogTop.CachedScenarios!.IndexOf (
+ UICatalogTop.CachedScenarios!.FirstOrDefault (
s =>
s.GetName ()
.Equals (options.Scenario, StringComparison.OrdinalIgnoreCase)
)!);
- UICatalogTopLevel.CachedSelectedScenario = (Scenario)Activator.CreateInstance (UICatalogTopLevel.CachedScenarios [item].GetType ())!;
+ UICatalogTop.CachedSelectedScenario = (Scenario)Activator.CreateInstance (UICatalogTop.CachedScenarios [item].GetType ())!;
- BenchmarkResults? results = RunScenario (UICatalogTopLevel.CachedSelectedScenario, options.Benchmark);
+ BenchmarkResults? results = RunScenario (UICatalogTop.CachedSelectedScenario, options.Benchmark);
if (results is { })
{
@@ -360,9 +360,9 @@ public class UICatalog
while (RunUICatalogTopLevel () is { } scenario)
{
VerifyObjectsWereDisposed ();
- Themes!.Theme = UICatalogTopLevel.CachedTheme!;
+ Themes!.Theme = UICatalogTop.CachedTheme!;
Apply ();
- scenario.TopLevelColorScheme = UICatalogTopLevel.CachedTopLevelColorScheme!;
+ scenario.TopLevelColorScheme = UICatalogTop.CachedTopLevelColorScheme!;
#if DEBUG_IDISPOSABLE
View.DebugIDisposable = true;
@@ -412,7 +412,7 @@ public class UICatalog
}
Application.Init (driverName: _forceDriver);
- scenario.TopLevelColorScheme = UICatalogTopLevel.CachedTopLevelColorScheme!;
+ scenario.TopLevelColorScheme = UICatalogTop.CachedTopLevelColorScheme!;
if (benchmark)
{
@@ -442,7 +442,7 @@ public class UICatalog
var maxScenarios = 5;
- foreach (Scenario s in UICatalogTopLevel.CachedScenarios!)
+ foreach (Scenario s in UICatalogTop.CachedScenarios!)
{
resultsList.Add (RunScenario (s, true)!);
maxScenarios--;
diff --git a/UICatalog/UICatalogTopLevel.cs b/UICatalog/UICatalogTop.cs
similarity index 98%
rename from UICatalog/UICatalogTopLevel.cs
rename to UICatalog/UICatalogTop.cs
index d6f65cc6f..0f29720c4 100644
--- a/UICatalog/UICatalogTopLevel.cs
+++ b/UICatalog/UICatalogTop.cs
@@ -18,7 +18,7 @@ namespace UICatalog;
/// This is the main UI Catalog app view. It is run fresh when the app loads (if a Scenario has not been passed on
/// the command line) and each time a Scenario ends.
///
-public class UICatalogTopLevel : Toplevel
+public class UICatalogTop : Toplevel
{
// When a scenario is run, the main app is killed. The static
// members are cached so that when the scenario exits the
@@ -32,7 +32,7 @@ public class UICatalogTopLevel : Toplevel
// Diagnostics
private static ViewDiagnosticFlags _diagnosticFlags;
- public UICatalogTopLevel ()
+ public UICatalogTop ()
{
_diagnosticFlags = Diagnostics;
@@ -563,6 +563,7 @@ public class UICatalogTopLevel : Toplevel
[JsonPropertyName ("UICatalog.StatusBar")]
public static bool ShowStatusBar { get; set; } = true;
+ private Shortcut? _shQuit;
private Shortcut? _shVersion;
private CheckBox? _force16ColorsShortcutCb;
@@ -582,6 +583,13 @@ public class UICatalogTopLevel : Toplevel
maximumContentDim: Dim.Func (() => statusBar.Visible ? 1 : 0));
// ReSharper restore All
+ _shQuit = new ()
+ {
+ CanFocus = false,
+ Title = "Quit",
+ Key = Application.QuitKey
+ };
+
_shVersion = new ()
{
Title = "Version Info",
@@ -616,12 +624,7 @@ public class UICatalogTopLevel : Toplevel
};
statusBar.Add (
- new Shortcut
- {
- CanFocus = false,
- Title = "Quit",
- Key = Application.QuitKey
- },
+ _shQuit,
statusBarShortcut,
new Shortcut
{
@@ -648,13 +651,20 @@ public class UICatalogTopLevel : Toplevel
ColorScheme = Colors.ColorSchemes [CachedTopLevelColorScheme!];
- ((Shortcut)_statusBar!.SubViews.ElementAt (0)).Key = Application.QuitKey;
- _statusBar.Visible = ShowStatusBar;
+ if (_shQuit is { })
+ {
+ _shQuit.Key = Application.QuitKey;
+ }
+
+ if (_statusBar is { })
+ {
+ _statusBar.Visible = ShowStatusBar;
+ }
_disableMouseCb!.CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.UnChecked;
_force16ColorsShortcutCb!.CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked;
- Application.Top!.SetNeedsDraw ();
+ Application.Top?.SetNeedsDraw ();
}
private void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a) { ConfigChanged (); }