From 313e7ecb0a085109fe904bb5fab42b36f34fd1af Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Thu, 28 Dec 2023 21:41:40 -0700 Subject: [PATCH 01/55] Much more efficient array insertion C# 12 feature --- .../MenuOperations/RemoveMenuItemOperation.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Operations/MenuOperations/RemoveMenuItemOperation.cs b/src/Operations/MenuOperations/RemoveMenuItemOperation.cs index 9bc1304c..7a8e7ed4 100644 --- a/src/Operations/MenuOperations/RemoveMenuItemOperation.cs +++ b/src/Operations/MenuOperations/RemoveMenuItemOperation.cs @@ -64,10 +64,12 @@ public override void Undo() return; } - var children = this.Parent.Children.ToList(); - - children.Insert(this.removedAtIdx, this.OperateOn); - this.Parent.Children = children.ToArray(); + this.Parent.Children = + [ + .. Parent.Children[ .. removedAtIdx ], + this.OperateOn, + .. Parent.Children[ removedAtIdx .. ] + ]; this.Bar?.SetNeedsDisplay(); // if any MenuBarItem were converted to vanilla MenuItem From 61b60c44e232f236efd7dd737cb7216f85c34813 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Thu, 28 Dec 2023 22:30:56 -0700 Subject: [PATCH 02/55] Avoid allocating an iterator --- src/MenuTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MenuTracker.cs b/src/MenuTracker.cs index ffe7d670..471f496b 100644 --- a/src/MenuTracker.cs +++ b/src/MenuTracker.cs @@ -142,7 +142,7 @@ internal static bool ConvertMenuBarItemToRegularItemIfEmpty(MenuBarItem bar, out added = null; // bar still has more children so don't convert - if (bar.Children.Any()) + if ( bar.Children.Length != 0 ) { return false; } From 22c1c052e74772959ca45c0ace4c3330c17795a9 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Thu, 28 Dec 2023 22:32:25 -0700 Subject: [PATCH 03/55] Add attribute for static analysis --- src/MenuTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MenuTracker.cs b/src/MenuTracker.cs index 471f496b..5ae6d6c2 100644 --- a/src/MenuTracker.cs +++ b/src/MenuTracker.cs @@ -137,7 +137,7 @@ public Dictionary ConvertEmptyMenus() /// The result of the conversion (same text, same index etc but /// instead of ). /// if conversion was possible (menu was empty and belonged to tracked menu). - internal static bool ConvertMenuBarItemToRegularItemIfEmpty(MenuBarItem bar, out MenuItem? added) + internal static bool ConvertMenuBarItemToRegularItemIfEmpty( MenuBarItem bar, [NotNullWhen( true )] out MenuItem? added ) { added = null; From 9be334e6275a20fec5cf1082bc6c81295e514ca0 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Thu, 28 Dec 2023 22:33:59 -0700 Subject: [PATCH 04/55] Only allocate one dictionary and use it --- src/MenuTracker.cs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/MenuTracker.cs b/src/MenuTracker.cs index 5ae6d6c2..2fd27404 100644 --- a/src/MenuTracker.cs +++ b/src/MenuTracker.cs @@ -106,22 +106,21 @@ public bool TryGetParent(MenuItem item, [NotNullWhen(true)]out MenuBar? hostBar, /// the substitution object (). See /// /// for more information. - public Dictionary ConvertEmptyMenus() + public Dictionary ConvertEmptyMenus( ) { - var toReturn = new Dictionary(); - + Dictionary dictionary = new( ); foreach (var b in this.bars) { foreach (var bi in b.Menus) { - foreach (var converted in this.ConvertEmptyMenus(b, bi)) + foreach (var converted in this.ConvertEmptyMenus(dictionary, b, bi)) { - toReturn.Add(converted.Key, converted.Value); + dictionary.TryAdd( converted.Key, converted.Value ); } } } - return toReturn; + return dictionary; } /// @@ -174,18 +173,16 @@ internal static bool ConvertMenuBarItemToRegularItemIfEmpty( MenuBarItem bar, [N } /// - private Dictionary ConvertEmptyMenus(MenuBar bar, MenuBarItem mbi) + private Dictionary ConvertEmptyMenus(Dictionary dictionary, MenuBar bar, MenuBarItem mbi) { - var toReturn = new Dictionary(); - foreach (var c in mbi.Children.OfType()) { - this.ConvertEmptyMenus(bar, c); + this.ConvertEmptyMenus(dictionary,bar, c); if ( ConvertMenuBarItemToRegularItemIfEmpty( c, out var added)) { if (added != null) { - toReturn.Add(c, added); + dictionary.TryAdd(c, added); } bar.CloseMenu(); @@ -193,7 +190,7 @@ private Dictionary ConvertEmptyMenus(MenuBar bar, MenuBar } } - return toReturn; + return dictionary; } private void MenuClosing(object? sender, MenuClosingEventArgs obj) @@ -204,7 +201,7 @@ private void MenuClosing(object? sender, MenuClosingEventArgs obj) private void MenuOpened(object? sender, MenuOpenedEventArgs obj) { this.CurrentlyOpenMenuItem = obj.MenuItem; - this.ConvertEmptyMenus(); + this.ConvertEmptyMenus( ); } private void MenuAllClosed(object? sender, EventArgs e) From bd123963c2967bb999e67f996106eae3eb0347dd Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Thu, 28 Dec 2023 22:34:40 -0700 Subject: [PATCH 05/55] This can't be null --- src/MenuTracker.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/MenuTracker.cs b/src/MenuTracker.cs index 2fd27404..0d03cbf9 100644 --- a/src/MenuTracker.cs +++ b/src/MenuTracker.cs @@ -178,12 +178,9 @@ private Dictionary ConvertEmptyMenus(Dictionary()) { this.ConvertEmptyMenus(dictionary,bar, c); - if ( ConvertMenuBarItemToRegularItemIfEmpty( c, out var added)) + if ( ConvertMenuBarItemToRegularItemIfEmpty( c, out MenuItem? added)) { - if (added != null) - { - dictionary.TryAdd(c, added); - } + dictionary.TryAdd( c, added ); bar.CloseMenu(); bar.OpenMenu(); From c2e768ccded037cb9496d0e109134485bbb7afc8 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Thu, 28 Dec 2023 22:35:37 -0700 Subject: [PATCH 06/55] Collection expression --- src/MenuTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MenuTracker.cs b/src/MenuTracker.cs index 0d03cbf9..358cc160 100644 --- a/src/MenuTracker.cs +++ b/src/MenuTracker.cs @@ -108,7 +108,7 @@ public bool TryGetParent(MenuItem item, [NotNullWhen(true)]out MenuBar? hostBar, /// for more information. public Dictionary ConvertEmptyMenus( ) { - Dictionary dictionary = new( ); + Dictionary dictionary = []; foreach (var b in this.bars) { foreach (var bi in b.Menus) From e06d46d2b43f464ba0f0742dbbf09bf09e96f983 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Fri, 29 Dec 2023 00:01:27 -0700 Subject: [PATCH 07/55] Eliminate more allocations Just set the object. --- src/MenuTracker.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/MenuTracker.cs b/src/MenuTracker.cs index 358cc160..3530a4b8 100644 --- a/src/MenuTracker.cs +++ b/src/MenuTracker.cs @@ -151,8 +151,7 @@ internal static bool ConvertMenuBarItemToRegularItemIfEmpty( MenuBarItem bar, [N return false; } - var children = parent.Children.ToList(); - var idx = children.IndexOf(bar); + int idx = Array.IndexOf( parent.Children, bar ); if (idx < 0) { @@ -160,14 +159,12 @@ internal static bool ConvertMenuBarItemToRegularItemIfEmpty( MenuBarItem bar, [N } // bar has no children so convert to MenuItem - added = new MenuItem { Title = bar.Title }; - added.Data = bar.Data; - added.Shortcut = bar.Shortcut; - - children.RemoveAt(idx); - children.Insert(idx, added); - - parent.Children = children.ToArray(); + parent.Children[ idx ] = added = new( ) + { + Title = bar.Title, + Data = bar.Data, + Shortcut = bar.Shortcut + }; return true; } From 1949364e9c75bb29989b966b6d8d1521cf88f337 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Fri, 29 Dec 2023 00:03:19 -0700 Subject: [PATCH 08/55] Deconstruct and make prettier --- src/MenuTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MenuTracker.cs b/src/MenuTracker.cs index 3530a4b8..f1d29c72 100644 --- a/src/MenuTracker.cs +++ b/src/MenuTracker.cs @@ -113,9 +113,9 @@ public Dictionary ConvertEmptyMenus( ) { foreach (var bi in b.Menus) { - foreach (var converted in this.ConvertEmptyMenus(dictionary, b, bi)) + foreach ( ( MenuBarItem? convertedMenuBarItem, MenuItem? convertedMenuItem ) in this.ConvertEmptyMenus( dictionary, b, bi ) ) { - dictionary.TryAdd( converted.Key, converted.Value ); + dictionary.TryAdd( convertedMenuBarItem, convertedMenuItem ); } } } From 32385473402963d4e5f25c37ed99433c4a69ca84 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Fri, 29 Dec 2023 00:25:02 -0700 Subject: [PATCH 09/55] This can now be private --- src/MenuTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MenuTracker.cs b/src/MenuTracker.cs index f1d29c72..4fc40a73 100644 --- a/src/MenuTracker.cs +++ b/src/MenuTracker.cs @@ -63,7 +63,7 @@ public void Register(MenuBar mb) /// The immediate parent of . /// Result may be a top level menu (e.g. File, View) /// or a sub-menu parent (e.g. View=>Windows). - public MenuBarItem? GetParent( MenuItem item, out MenuBar? hostBar ) + private MenuBarItem? GetParent( MenuItem item, out MenuBar? hostBar ) { foreach (var bar in this.bars) { From f1825a11899f267f9f0594a5579e6e744d89b09a Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Fri, 29 Dec 2023 00:26:24 -0700 Subject: [PATCH 10/55] XmlDoc for this (mostly copied from the old method --- src/MenuTracker.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/MenuTracker.cs b/src/MenuTracker.cs index 4fc40a73..776115af 100644 --- a/src/MenuTracker.cs +++ b/src/MenuTracker.cs @@ -83,7 +83,23 @@ public void Register(MenuBar mb) return null; } - public bool TryGetParent(MenuItem item, [NotNullWhen(true)]out MenuBar? hostBar, [NotNullWhen(true)] out MenuBarItem? parentItem) + /// + /// Searches child items of all MenuBars tracked by this class to try and find the parent of the item passed. + /// + /// The item whose parent you want to find. + /// + /// When this method returns true, the that owns .
Otherwise, if + /// not found or parent not registered (see ). + /// + /// + /// When this method returns , the immediate parent of .
Otherwise, + /// + /// + /// + /// Search is recursive and dips into sub-menus.
For sub-menus it is the immediate parent that is returned. + ///
+ /// A indicating if the search was successful or not. + public bool TryGetParent( MenuItem item, [NotNullWhen( true )] out MenuBar? hostBar, [NotNullWhen( true )] out MenuBarItem? parentItem ) { var parentCandidate = GetParent( item, out hostBar ); if ( parentCandidate is null ) From c9cb983174f99ee8a0de704b8e1278ed0e48f2b1 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Fri, 29 Dec 2023 00:49:55 -0700 Subject: [PATCH 11/55] =?UTF-8?q?Null=20propagation=20removes=20an=20inden?= =?UTF-8?q?tation=20level=20here=20=F0=9F=A5=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MenuOperations/RemoveMenuItemOperation.cs | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/Operations/MenuOperations/RemoveMenuItemOperation.cs b/src/Operations/MenuOperations/RemoveMenuItemOperation.cs index 7a8e7ed4..aa22b035 100644 --- a/src/Operations/MenuOperations/RemoveMenuItemOperation.cs +++ b/src/Operations/MenuOperations/RemoveMenuItemOperation.cs @@ -139,27 +139,24 @@ protected override bool DoImpl() } // if a top level menu now has no children - if (this.Bar != null) + var empty = this.Bar?.Menus.Where(bi => bi.Children.Length == 0).ToArray(); + if (empty?.Any() == true) { - var empty = this.Bar.Menus.Where(bi => bi.Children.Length == 0).ToArray(); - if (empty.Any()) - { - // remember where they were - this.prunedEmptyTopLevelMenus = empty.ToDictionary(e => Array.IndexOf(this.Bar.Menus, e), v => v); + // remember where they were + this.prunedEmptyTopLevelMenus = empty.ToDictionary(e => Array.IndexOf(this.Bar.Menus, e), v => v); - // and remove them - this.Bar.Menus = this.Bar.Menus.Except(this.prunedEmptyTopLevelMenus.Values).ToArray(); - } + // and remove them + this.Bar.Menus = this.Bar.Menus.Except(this.prunedEmptyTopLevelMenus.Values).ToArray(); + } - // if we just removed the last menu header - // leaving a completely blank menu bar - if (this.Bar.Menus.Length == 0 && this.Bar.SuperView != null) - { - // remove the bar completely - this.Bar.CloseMenu(); - this.barRemovedFrom = this.Bar.SuperView; - this.barRemovedFrom.Remove(this.Bar); - } + // if we just removed the last menu header + // leaving a completely blank menu bar + if (this.Bar?.Menus.Length == 0 && this.Bar.SuperView != null) + { + // remove the bar completely + this.Bar.CloseMenu(); + this.barRemovedFrom = this.Bar.SuperView; + this.barRemovedFrom.Remove(this.Bar); } return true; From 724f8d3a3fbc91d4122a41e1232fafb16859748d Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Fri, 29 Dec 2023 00:51:02 -0700 Subject: [PATCH 12/55] Simplify with collection expression Potentially fewer allocations, as well --- .../MenuOperations/RemoveMenuItemOperation.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Operations/MenuOperations/RemoveMenuItemOperation.cs b/src/Operations/MenuOperations/RemoveMenuItemOperation.cs index aa22b035..35d696b2 100644 --- a/src/Operations/MenuOperations/RemoveMenuItemOperation.cs +++ b/src/Operations/MenuOperations/RemoveMenuItemOperation.cs @@ -125,12 +125,12 @@ protected override bool DoImpl() return false; } - var children = this.Parent.Children.ToList(); - - this.removedAtIdx = Math.Max(0, children.IndexOf(this.OperateOn)); - - children.Remove(this.OperateOn); - this.Parent.Children = children.ToArray(); + this.removedAtIdx = Math.Max( 0, Array.IndexOf( Parent.Children, OperateOn ) ); + this.Parent.Children = + [ + .. Parent.Children[ ..removedAtIdx ], + .. Parent.Children[ ( removedAtIdx + 1 ).. ] + ]; this.Bar?.SetNeedsDisplay(); if (this.Bar != null) From 81bba5489f8b5deaaf4d291c5a84679e05c951c3 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 14:43:27 -0700 Subject: [PATCH 13/55] OK Dependabot! We hear you! Bump nuget packages. Tests confirmed still passing. --- src/TerminalGuiDesigner.csproj | 2 +- tests/UnitTests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TerminalGuiDesigner.csproj b/src/TerminalGuiDesigner.csproj index c2cd0d4b..4583e5bd 100644 --- a/src/TerminalGuiDesigner.csproj +++ b/src/TerminalGuiDesigner.csproj @@ -145,7 +145,7 @@ - + diff --git a/tests/UnitTests.csproj b/tests/UnitTests.csproj index e4655727..4c2cb88e 100644 --- a/tests/UnitTests.csproj +++ b/tests/UnitTests.csproj @@ -29,7 +29,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From f7e25542aaf4a4d4ccc441674e8d5013b30d8741 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 19:38:24 -0700 Subject: [PATCH 14/55] Add an unregister method --- src/MenuTracker.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/MenuTracker.cs b/src/MenuTracker.cs index 776115af..109c3b07 100644 --- a/src/MenuTracker.cs +++ b/src/MenuTracker.cs @@ -47,6 +47,22 @@ public void Register(MenuBar mb) this.bars.Add(mb); } + /// + /// Unregisters listeners for . + /// + /// to stop tracking. + public void UnregisterMenuBar( MenuBar? mb ) + { + if ( !bars.TryTake( out mb ) ) + { + return; + } + + mb.MenuAllClosed -= MenuAllClosed; + mb.MenuOpened -= MenuOpened; + mb.MenuClosing -= MenuClosing; + } + /// /// /// Searches child items of all MenuBars tracked by this class From e6555eb831fbb59154519e681cd09b9c666812aa Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 19:40:31 -0700 Subject: [PATCH 15/55] Convert and improve this big test. Should probably be split up into multiple tests or at least have tests added for the individual actions it takes to prove them in isolation. --- tests/MenuBarTests.cs | 173 +++++++++++++++++++++++++++++++++--------- 1 file changed, 139 insertions(+), 34 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index a2a2e467..d6e2cf98 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -3,8 +3,8 @@ namespace UnitTests; [TestFixture] -[TestOf(typeof(OperationManager))] -[Category("UI")] +[TestOf( typeof( OperationManager ) )] +[Category( "UI" )] internal class MenuBarTests : Tests { [Test] @@ -147,18 +147,21 @@ public void RoundTrip_PreserveMenuItems_WithSubmenus( ) } [Test] - [TestOf(typeof(MenuTracker))] + [TestOf( typeof( MenuTracker ) )] + // TODO: Break this one up into smaller units at some point. public void TestMenuOperations() { ViewToCode viewToCode = new (); - FileInfo file = new ($"{nameof(TestMenuOperations)}.cs"); + FileInfo file = new( $"{nameof( TestMenuOperations )}.cs" ); Design designOut = viewToCode.GenerateNewView( file, "YourNamespace", typeof( Dialog ) ); Assume.That( designOut, Is.Not.Null.And.InstanceOf( ) ); Assume.That( designOut.View, Is.Not.Null.And.InstanceOf( ) ); MenuBar mbOut = ViewFactory.Create( ); Assume.That( mbOut, Is.Not.Null.And.InstanceOf( ) ); + Assume.That( OperationManager.Instance.UndoStackSize, Is.Zero ); + Assume.That( OperationManager.Instance.RedoStackSize, Is.Zero ); Assert.Warn( "MenuTracker.Instance.CurrentlyOpenMenuItem cannot be guaranteed null at this time. See https://github.com/gui-cs/TerminalGuiDesigner/issues/270" ); // TODO: Enable this pre-condition once MenuTracker changes are implemented. @@ -168,69 +171,171 @@ public void TestMenuOperations() MenuTracker.Instance.Register( mbOut ); // 1 visible root menu (e.g. File) - ClassicAssert.AreEqual(1, mbOut.Menus.Length); + Assert.That( mbOut.Menus, Has.Exactly( 1 ).InstanceOf( ) ); // 1 child menu item (e.g. Open) - ClassicAssert.AreEqual(1, mbOut.Menus[0].Children.Length); + Assert.That( mbOut.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); + + MenuItem? orig = mbOut.Menus[ 0 ].Children[ 0 ]; + Assert.That( orig, Is.Not.Null.And.InstanceOf( ) ); - MenuItem? orig = mbOut.Menus[0].Children[0]; + AddMenuItemOperation? addMenuItemOperation = null; + Assert.That( ( ) => addMenuItemOperation = new( mbOut.Menus[ 0 ].Children[ 0 ] ), Throws.Nothing ); + Assert.That( addMenuItemOperation, Is.Not.Null.And.InstanceOf( ) ); - OperationManager.Instance.Do( - new AddMenuItemOperation(mbOut.Menus[0].Children[0])); + bool addMenuItemOperationSucceeded = false; + Assert.That( ( ) => addMenuItemOperationSucceeded = OperationManager.Instance.Do( addMenuItemOperation! ), Throws.Nothing ); + Assert.That( addMenuItemOperationSucceeded ); // Now 2 child menu item - ClassicAssert.AreEqual(2, mbOut.Menus[0].Children.Length); - ClassicAssert.AreSame(orig, mbOut.Menus[0].Children[0]); // original is still at top + Assert.That( mbOut.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.SameAs( orig ) ); // original is still at top + Assert.That( mbOut.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.Not.SameAs( orig ) ); + } ); + + Assert.Multiple( ( ) => + { + Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 1 ) ); + Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); + } ); OperationManager.Instance.Undo(); + Assert.Multiple( ( ) => + { + Assert.That( OperationManager.Instance.UndoStackSize, Is.Zero ); + Assert.That( OperationManager.Instance.RedoStackSize, Is.EqualTo( 1 ) ); + } ); + // Now only 1 child menu item - ClassicAssert.AreEqual(1, mbOut.Menus[0].Children.Length); - ClassicAssert.AreSame(orig, mbOut.Menus[0].Children[0]); // original is still at top + Assert.That( mbOut.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.SameAs( orig ) ); // original is still at top OperationManager.Instance.Redo(); - // Now 2 child menu item - ClassicAssert.AreEqual(2, mbOut.Menus[0].Children.Length); - ClassicAssert.AreSame(orig, mbOut.Menus[0].Children[0]); // original is still at top + Assert.Multiple( ( ) => + { + Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 1 ) ); + Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); + } ); + + // Now 2 child menu items again + Assert.That( mbOut.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.SameAs( orig ) ); // original is still at top + Assert.That( mbOut.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.Not.SameAs( orig ) ); // original is still at top + } ); // Now test moving an item around - MenuItem? toMove = mbOut.Menus[0].Children[1]; + MenuItem? toMove = mbOut.Menus[ 0 ].Children[ 1 ]; + Assume.That( toMove, Is.Not.Null.And.InstanceOf( ) ); // Move second menu item up - MoveMenuItemOperation up = new MoveMenuItemOperation(toMove, true); - ClassicAssert.IsFalse(up.IsImpossible); - OperationManager.Instance.Do(up); + MoveMenuItemOperation? up = null; + Assert.That( ( ) => up = new( toMove, true ), Throws.Nothing ); + Assert.That( up, Is.Not.Null.And.InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( up!.Bar, Is.SameAs( mbOut ) ); + Assert.That( up.IsImpossible, Is.False ); + } ); + + bool moveUpSucceeded = false; + Assert.That( ( ) => moveUpSucceeded = OperationManager.Instance.Do( up ), Throws.Nothing ); + Assert.That( moveUpSucceeded ); + + Assert.Multiple( ( ) => + { + Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 2 ) ); + Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); + } ); // Original one should now be bottom - ClassicAssert.AreSame(orig, mbOut.Menus[0].Children[1]); + Assume.That( orig, Is.Not.SameAs( toMove ) ); + Assert.That( mbOut.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.Not.SameAs( orig ) ); + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.SameAs( toMove ) ); + Assert.That( mbOut.Menus[ 0 ].Children[ 1 ], Is.SameAs( orig ) ); + } ); // can't move top one up - ClassicAssert.IsTrue(new MoveMenuItemOperation(toMove, true).IsImpossible); + MoveMenuItemOperation? impossibleMoveUpOperation = null; + Assert.That( ( ) => impossibleMoveUpOperation = new( toMove, true ), Throws.Nothing ); + Assert.That( impossibleMoveUpOperation, Is.Not.Null.And.InstanceOf( ) ); + Assert.That( impossibleMoveUpOperation!.IsImpossible ); + // cant move bottom one down - ClassicAssert.IsTrue(new MoveMenuItemOperation(mbOut.Menus[0].Children[1], false).IsImpossible); + MoveMenuItemOperation? impossibleMoveDownOperation = null; + Assert.That( ( ) => impossibleMoveDownOperation = new( mbOut.Menus[ 0 ].Children[ 1 ], false ), Throws.Nothing ); + Assert.That( impossibleMoveDownOperation, Is.Not.Null.And.InstanceOf( ) ); + Assert.That( impossibleMoveDownOperation!.IsImpossible ); - OperationManager.Instance.Undo(); + Assert.That( static ( ) => OperationManager.Instance.Undo( ), Throws.Nothing ); // Original one should be back on top - ClassicAssert.AreSame(orig, mbOut.Menus[0].Children[0]); + Assert.That( mbOut.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.SameAs( orig ) ); + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.Not.SameAs( toMove ) ); + Assert.That( mbOut.Menus[ 0 ].Children[ 1 ], Is.SameAs( toMove ) ); + } ); // test moving the top one down - MenuItem? toMove2 = mbOut.Menus[0].Children[1]; + MenuItem? toMove2 = mbOut.Menus[ 0 ].Children[ 1 ]; // Move first menu item down - MoveMenuItemOperation down = new MoveMenuItemOperation(toMove2, true); - ClassicAssert.IsFalse(down.IsImpossible); - OperationManager.Instance.Do(down); + MoveMenuItemOperation? down = null; + Assert.That( ( ) => down = new( toMove2, true ), Throws.Nothing ); + Assert.That( down, Is.Not.Null.And.InstanceOf( ) ); + Assert.That( down!.IsImpossible, Is.False ); + + Assert.Multiple( ( ) => + { + Assert.That( OperationManager.Instance.RedoStackSize, Is.EqualTo( 1 ) ); + Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 1 ) ); + } ); + + bool moveDownSucceeded = false; + Assert.That( ( ) => moveDownSucceeded = OperationManager.Instance.Do( down ), Throws.Nothing ); + Assert.That( moveDownSucceeded ); + Assert.Multiple( ( ) => + { + Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); + Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 2 ) ); + } ); // Original one should now be bottom - ClassicAssert.AreSame(orig, mbOut.Menus[0].Children[1]); - ClassicAssert.AreNotSame(orig, mbOut.Menus[0].Children[0]); + Assert.That( mbOut.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.SameAs( toMove2 ) ); + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.Not.SameAs( orig ) ); + Assert.That( mbOut.Menus[ 0 ].Children[ 1 ], Is.SameAs( orig ) ); + } ); + + Assert.That( static ( ) => OperationManager.Instance.Undo( ), Throws.Nothing ); + + Assert.Multiple( static ( ) => + { + Assert.That( OperationManager.Instance.RedoStackSize, Is.EqualTo( 1 ) ); + Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 1 ) ); + } ); - OperationManager.Instance.Undo(); // should be back to how we started now - ClassicAssert.AreSame(orig, mbOut.Menus[0].Children[0]); - ClassicAssert.AreNotSame(orig, mbOut.Menus[0].Children[1]); + Assert.That( mbOut.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.SameAs( orig ) ); + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.Not.SameAs( toMove2 ) ); + Assert.That( mbOut.Menus[ 0 ].Children[ 1 ], Is.SameAs( toMove2 ) ); + } ); + } private MenuBar GetMenuBar() From 6ce10d6da05b1b64f71caeaaf90b6071b0b3a950 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 19:40:57 -0700 Subject: [PATCH 16/55] Use using to dispose of this --- tests/MenuBarTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index d6e2cf98..34d7b8c5 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -158,7 +158,7 @@ public void TestMenuOperations() Assume.That( designOut, Is.Not.Null.And.InstanceOf( ) ); Assume.That( designOut.View, Is.Not.Null.And.InstanceOf( ) ); - MenuBar mbOut = ViewFactory.Create( ); + using MenuBar mbOut = ViewFactory.Create( ); Assume.That( mbOut, Is.Not.Null.And.InstanceOf( ) ); Assume.That( OperationManager.Instance.UndoStackSize, Is.Zero ); Assume.That( OperationManager.Instance.RedoStackSize, Is.Zero ); From 4ae4e45134c67bcb8d0400f15a10209fb4f86f2d Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 19:41:11 -0700 Subject: [PATCH 17/55] First usage of the new unregister method --- tests/MenuBarTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 34d7b8c5..2f2c387f 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -336,6 +336,7 @@ public void TestMenuOperations() Assert.That( mbOut.Menus[ 0 ].Children[ 1 ], Is.SameAs( toMove2 ) ); } ); + MenuTracker.Instance.UnregisterMenuBar( mbOut ); } private MenuBar GetMenuBar() From 27c0582b4639370d33088c0b0d32ad133f5f7753 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 19:47:47 -0700 Subject: [PATCH 18/55] Sorted members by design guides ONLY --- tests/MenuBarTests.cs | 352 +++++++++++++++++++++--------------------- 1 file changed, 176 insertions(+), 176 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 2f2c387f..396779df 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -146,6 +146,94 @@ public void RoundTrip_PreserveMenuItems_WithSubmenus( ) Is.EqualTo( ( (MenuBarItem)mbOut.Menus[ 0 ].Children[ 0 ] ).Children[ 0 ].Title ) ); } + [Test] + public void TestDeletingLastMenuItem_ShouldRemoveWholeBar() + { + var bar = this.GetMenuBar(out Design root); + + var mi = bar.Menus[0].Children[0]; + + ClassicAssert.Contains(bar, root.View.Subviews.ToArray(), + "The MenuBar should be on the main view being edited"); + + var cmd = new RemoveMenuItemOperation(mi); + ClassicAssert.IsTrue(cmd.Do()); + + ClassicAssert.IsEmpty(bar.Menus, "Expected menu bar header (File) to be removed along with it's last (only) child"); + + ClassicAssert.IsFalse( + root.View.Subviews.Contains(bar), + "Now that the MenuBar is completely empty it should be automatically removed"); + + cmd.Undo(); + + ClassicAssert.Contains(bar, root.View.Subviews.ToArray(), + "Undo should put the MenuBar back on the view again"); + } + + [Test] + public void TestDeletingMenuItemFromSubmenu_AllSubmenuChild() + { + var bar = this.GetMenuBarWithSubmenuItems(out var head2, out var topChild); + var bottomChild = head2.Children[1]; + + ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); + ClassicAssert.AreEqual(typeof(MenuBarItem), bar.Menus[0].Children[1].GetType()); + ClassicAssert.AreEqual(2, head2.Children.Length); + ClassicAssert.AreSame(topChild, head2.Children[0]); + + var cmd1 = new RemoveMenuItemOperation(topChild); + ClassicAssert.IsTrue(cmd1.Do()); + + var cmd2 = new RemoveMenuItemOperation(bottomChild); + ClassicAssert.IsTrue(cmd2.Do()); + + // Deleting both children should convert us from + // a dropdown submenu to just a regular MenuItem + ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); + ClassicAssert.AreEqual(typeof(MenuItem), bar.Menus[0].Children[1].GetType()); + + cmd2.Undo(); + + // should bring the bottom one back + ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); + ClassicAssert.AreEqual(typeof(MenuBarItem), bar.Menus[0].Children[1].GetType()); + ClassicAssert.AreSame(bottomChild, ((MenuBarItem)bar.Menus[0].Children[1]).Children[0]); + + cmd1.Undo(); + + // Both submenu items should now be back + ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); + ClassicAssert.AreEqual(typeof(MenuBarItem), bar.Menus[0].Children[1].GetType()); + ClassicAssert.AreSame(topChild, ((MenuBarItem)bar.Menus[0].Children[1]).Children[0]); + ClassicAssert.AreSame(bottomChild, ((MenuBarItem)bar.Menus[0].Children[1]).Children[1]); + } + + [Test] + public void TestDeletingMenuItemFromSubmenu_TopChild() + { + var bar = this.GetMenuBarWithSubmenuItems(out var head2, out var topChild); + + ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); + ClassicAssert.AreEqual(2, head2.Children.Length); + ClassicAssert.AreSame(topChild, head2.Children[0]); + + var cmd = new RemoveMenuItemOperation(topChild); + ClassicAssert.IsTrue(cmd.Do()); + + // Delete the top child should leave only 1 in submenu + ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); + ClassicAssert.AreEqual(1, head2.Children.Length); + ClassicAssert.AreNotSame(topChild, head2.Children[0]); + + cmd.Undo(); + + // should come back now + ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); + ClassicAssert.AreEqual(2, head2.Children.Length); + ClassicAssert.AreSame(topChild, head2.Children[0]); + } + [Test] [TestOf( typeof( MenuTracker ) )] // TODO: Break this one up into smaller units at some point. @@ -339,66 +427,54 @@ public void TestMenuOperations() MenuTracker.Instance.UnregisterMenuBar( mbOut ); } - private MenuBar GetMenuBar() - { - return this.GetMenuBar(out _); - } - - private MenuBar GetMenuBar(out Design root) + [Test] + public void TestMoveMenuItemLeft_CannotMoveRootItems() { - root = Get10By10View(); - - var bar = ViewFactory.Create( ); - var addBarCmd = new AddViewOperation(bar, root, "mb"); - ClassicAssert.IsTrue(addBarCmd.Do()); + var bar = this.GetMenuBar(); - // Expect ViewFactory to have created a single - // placeholder menu item - ClassicAssert.AreEqual(1, bar.Menus.Length); - ClassicAssert.AreEqual(1, bar.Menus[0].Children.Length); + var mi = bar.Menus[0].Children[0]; - return bar; + // cannot move a root item + ClassicAssert.IsFalse(new MoveMenuItemLeftOperation( + bar.Menus[0].Children[0]) + .Do()); } - /// - /// Tests removing the last menu item (i.e. 'Do Something') - /// under the only remaining menu header (e.g. 'File F9') - /// should result in a completely empty menu bar and be undoable - /// [Test] - public void TestRemoveFinalMenuItemOnBar() + public void TestMoveMenuItemLeft_MoveTopChild() { - var bar = this.GetMenuBar(); + var bar = this.GetMenuBarWithSubmenuItems(out var head2, out var topChild); - var fileMenu = bar.Menus[0]; - var placeholderMenuItem = fileMenu.Children[0]; + ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); + ClassicAssert.AreEqual(2, head2.Children.Length); + ClassicAssert.AreSame(topChild, head2.Children[0]); - var remove = new RemoveMenuItemOperation(placeholderMenuItem); + var cmd = new MoveMenuItemLeftOperation(topChild); + ClassicAssert.IsTrue(cmd.Do()); - // we are able to remove the last one - ClassicAssert.IsTrue(remove.Do()); - ClassicAssert.IsEmpty(bar.Menus, "menu bar should now be completely empty"); + // move the top child left should pull + // it out of the submenu and onto the root + ClassicAssert.AreEqual(4, bar.Menus[0].Children.Length); + ClassicAssert.AreEqual(1, head2.Children.Length); - remove.Undo(); + // it should be pulled out underneath its parent + // and preserve its (Name) and Shortcuts + ClassicAssert.AreEqual(topChild.Title, bar.Menus[0].Children[2].Title); + ClassicAssert.AreEqual(topChild.Data, bar.Menus[0].Children[2].Data); + ClassicAssert.AreEqual(topChild.Shortcut, bar.Menus[0].Children[2].Shortcut); + ClassicAssert.AreSame(topChild, bar.Menus[0].Children[2]); - // should be back to where we started - ClassicAssert.AreEqual(1, bar.Menus.Length); - ClassicAssert.AreEqual(1, bar.Menus[0].Children.Length); - ClassicAssert.AreSame(placeholderMenuItem, bar.Menus[0].Children[0]); - } + // undoing command should return us to + // previous state + cmd.Undo(); - /// - /// Tests that when there is only one menu item - /// that it cannot be moved into a submenu - /// - [Test] - public void TestMoveMenuItemRight_CannotMoveLast() - { - var bar = this.GetMenuBar(); + ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); + ClassicAssert.AreEqual(2, head2.Children.Length); - var mi = bar.Menus[0].Children[0]; - var cmd = new MoveMenuItemRightOperation(mi); - ClassicAssert.IsFalse(cmd.Do()); + ClassicAssert.AreEqual(topChild.Title, head2.Children[0].Title); + ClassicAssert.AreEqual(topChild.Data, head2.Children[0].Data); + ClassicAssert.AreEqual(topChild.Shortcut, head2.Children[0].Shortcut); + ClassicAssert.AreSame(topChild, head2.Children[0]); } [Test] @@ -444,17 +520,66 @@ public void TestMoveMenuItemRight_CannotMoveElementZero() ClassicAssert.AreNotSame(mi, bar.Menus[0].Children[1]); } + /// + /// Tests that when there is only one menu item + /// that it cannot be moved into a submenu + /// [Test] - public void TestMoveMenuItemLeft_CannotMoveRootItems() + public void TestMoveMenuItemRight_CannotMoveLast() { var bar = this.GetMenuBar(); var mi = bar.Menus[0].Children[0]; + var cmd = new MoveMenuItemRightOperation(mi); + ClassicAssert.IsFalse(cmd.Do()); + } - // cannot move a root item - ClassicAssert.IsFalse(new MoveMenuItemLeftOperation( - bar.Menus[0].Children[0]) - .Do()); + /// + /// Tests removing the last menu item (i.e. 'Do Something') + /// under the only remaining menu header (e.g. 'File F9') + /// should result in a completely empty menu bar and be undoable + /// + [Test] + public void TestRemoveFinalMenuItemOnBar() + { + var bar = this.GetMenuBar(); + + var fileMenu = bar.Menus[0]; + var placeholderMenuItem = fileMenu.Children[0]; + + var remove = new RemoveMenuItemOperation(placeholderMenuItem); + + // we are able to remove the last one + ClassicAssert.IsTrue(remove.Do()); + ClassicAssert.IsEmpty(bar.Menus, "menu bar should now be completely empty"); + + remove.Undo(); + + // should be back to where we started + ClassicAssert.AreEqual(1, bar.Menus.Length); + ClassicAssert.AreEqual(1, bar.Menus[0].Children.Length); + ClassicAssert.AreSame(placeholderMenuItem, bar.Menus[0].Children[0]); + } + + private MenuBar GetMenuBar() + { + return this.GetMenuBar(out _); + } + + private MenuBar GetMenuBar(out Design root) + { + root = Get10By10View(); + + var bar = ViewFactory.Create( ); + var addBarCmd = new AddViewOperation(bar, root, "mb"); + ClassicAssert.IsTrue(addBarCmd.Do()); + + // Expect ViewFactory to have created a single + // placeholder menu item + ClassicAssert.AreEqual(1, bar.Menus.Length); + ClassicAssert.AreEqual(1, bar.Menus[0].Children.Length); + + return bar; } private MenuBar GetMenuBarWithSubmenuItems(out MenuBarItem head2, out MenuItem topChild) @@ -496,129 +621,4 @@ Head3 Child2 return bar; } - - [Test] - public void TestMoveMenuItemLeft_MoveTopChild() - { - var bar = this.GetMenuBarWithSubmenuItems(out var head2, out var topChild); - - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(2, head2.Children.Length); - ClassicAssert.AreSame(topChild, head2.Children[0]); - - var cmd = new MoveMenuItemLeftOperation(topChild); - ClassicAssert.IsTrue(cmd.Do()); - - // move the top child left should pull - // it out of the submenu and onto the root - ClassicAssert.AreEqual(4, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(1, head2.Children.Length); - - // it should be pulled out underneath its parent - // and preserve its (Name) and Shortcuts - ClassicAssert.AreEqual(topChild.Title, bar.Menus[0].Children[2].Title); - ClassicAssert.AreEqual(topChild.Data, bar.Menus[0].Children[2].Data); - ClassicAssert.AreEqual(topChild.Shortcut, bar.Menus[0].Children[2].Shortcut); - ClassicAssert.AreSame(topChild, bar.Menus[0].Children[2]); - - // undoing command should return us to - // previous state - cmd.Undo(); - - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(2, head2.Children.Length); - - ClassicAssert.AreEqual(topChild.Title, head2.Children[0].Title); - ClassicAssert.AreEqual(topChild.Data, head2.Children[0].Data); - ClassicAssert.AreEqual(topChild.Shortcut, head2.Children[0].Shortcut); - ClassicAssert.AreSame(topChild, head2.Children[0]); - } - - [Test] - public void TestDeletingMenuItemFromSubmenu_TopChild() - { - var bar = this.GetMenuBarWithSubmenuItems(out var head2, out var topChild); - - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(2, head2.Children.Length); - ClassicAssert.AreSame(topChild, head2.Children[0]); - - var cmd = new RemoveMenuItemOperation(topChild); - ClassicAssert.IsTrue(cmd.Do()); - - // Delete the top child should leave only 1 in submenu - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(1, head2.Children.Length); - ClassicAssert.AreNotSame(topChild, head2.Children[0]); - - cmd.Undo(); - - // should come back now - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(2, head2.Children.Length); - ClassicAssert.AreSame(topChild, head2.Children[0]); - } - - [Test] - public void TestDeletingMenuItemFromSubmenu_AllSubmenuChild() - { - var bar = this.GetMenuBarWithSubmenuItems(out var head2, out var topChild); - var bottomChild = head2.Children[1]; - - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(typeof(MenuBarItem), bar.Menus[0].Children[1].GetType()); - ClassicAssert.AreEqual(2, head2.Children.Length); - ClassicAssert.AreSame(topChild, head2.Children[0]); - - var cmd1 = new RemoveMenuItemOperation(topChild); - ClassicAssert.IsTrue(cmd1.Do()); - - var cmd2 = new RemoveMenuItemOperation(bottomChild); - ClassicAssert.IsTrue(cmd2.Do()); - - // Deleting both children should convert us from - // a dropdown submenu to just a regular MenuItem - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(typeof(MenuItem), bar.Menus[0].Children[1].GetType()); - - cmd2.Undo(); - - // should bring the bottom one back - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(typeof(MenuBarItem), bar.Menus[0].Children[1].GetType()); - ClassicAssert.AreSame(bottomChild, ((MenuBarItem)bar.Menus[0].Children[1]).Children[0]); - - cmd1.Undo(); - - // Both submenu items should now be back - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(typeof(MenuBarItem), bar.Menus[0].Children[1].GetType()); - ClassicAssert.AreSame(topChild, ((MenuBarItem)bar.Menus[0].Children[1]).Children[0]); - ClassicAssert.AreSame(bottomChild, ((MenuBarItem)bar.Menus[0].Children[1]).Children[1]); - } - - [Test] - public void TestDeletingLastMenuItem_ShouldRemoveWholeBar() - { - var bar = this.GetMenuBar(out Design root); - - var mi = bar.Menus[0].Children[0]; - - ClassicAssert.Contains(bar, root.View.Subviews.ToArray(), - "The MenuBar should be on the main view being edited"); - - var cmd = new RemoveMenuItemOperation(mi); - ClassicAssert.IsTrue(cmd.Do()); - - ClassicAssert.IsEmpty(bar.Menus, "Expected menu bar header (File) to be removed along with it's last (only) child"); - - ClassicAssert.IsFalse( - root.View.Subviews.Contains(bar), - "Now that the MenuBar is completely empty it should be automatically removed"); - - cmd.Undo(); - - ClassicAssert.Contains(bar, root.View.Subviews.ToArray(), - "Undo should put the MenuBar back on the view again"); - } } \ No newline at end of file From faece781fb871d77b04a0f6ac091cea1171e6c0c Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 19:49:00 -0700 Subject: [PATCH 19/55] Annotate this test method --- tests/MenuBarTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 396779df..5351a0cb 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -147,6 +147,7 @@ public void RoundTrip_PreserveMenuItems_WithSubmenus( ) } [Test] + [TestOf( typeof( RemoveMenuItemOperation ) )] public void TestDeletingLastMenuItem_ShouldRemoveWholeBar() { var bar = this.GetMenuBar(out Design root); From abab514c06667bc9b402bc38ce066350d129e454 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 19:53:00 -0700 Subject: [PATCH 20/55] A few pre-conditions --- tests/MenuBarTests.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 5351a0cb..f5b7b504 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -148,16 +148,25 @@ public void RoundTrip_PreserveMenuItems_WithSubmenus( ) [Test] [TestOf( typeof( RemoveMenuItemOperation ) )] - public void TestDeletingLastMenuItem_ShouldRemoveWholeBar() + public void DeletingLastMenuItem_ShouldRemoveWholeBar() { - var bar = this.GetMenuBar(out Design root); - - var mi = bar.Menus[0].Children[0]; + MenuBar bar = this.GetMenuBar( out Design root ); + Assume.That( bar, Is.Not.Null.And.InstanceOf( ) ); + Assume.That( root, Is.Not.Null.And.InstanceOf( ) ); + Assume.That( root.View.Subviews, Has.Exactly( 1 ).InstanceOf( ) ); + Assume.That( root.View.Subviews[ 0 ], Is.Not.Null.And.SameAs( bar ) ); + Assume.That( bar.Menus, Is.Not.Null ); + Assume.That( bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); + Assume.That( bar.Menus[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + Assume.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assume.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + + MenuItem? mi = bar.Menus[ 0 ].Children[ 0 ]; ClassicAssert.Contains(bar, root.View.Subviews.ToArray(), "The MenuBar should be on the main view being edited"); - var cmd = new RemoveMenuItemOperation(mi); + RemoveMenuItemOperation cmd = new RemoveMenuItemOperation(mi); ClassicAssert.IsTrue(cmd.Do()); ClassicAssert.IsEmpty(bar.Menus, "Expected menu bar header (File) to be removed along with it's last (only) child"); From bbbae8ec2f17ef677854d95426a2bc9fcfa967ab Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 19:57:56 -0700 Subject: [PATCH 21/55] Pre-condition has guaranteed this can't be null --- tests/MenuBarTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index f5b7b504..26ae434d 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -161,7 +161,7 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar() Assume.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); Assume.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); - MenuItem? mi = bar.Menus[ 0 ].Children[ 0 ]; + MenuItem mi = bar.Menus[ 0 ].Children[ 0 ]; ClassicAssert.Contains(bar, root.View.Subviews.ToArray(), "The MenuBar should be on the main view being edited"); From 2458a4af9c476eae1b0926830fdac5828cf393f0 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 19:59:13 -0700 Subject: [PATCH 22/55] This is guaranteed by the pre-conditions --- tests/MenuBarTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 26ae434d..d1070bc4 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -163,9 +163,6 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar() MenuItem mi = bar.Menus[ 0 ].Children[ 0 ]; - ClassicAssert.Contains(bar, root.View.Subviews.ToArray(), - "The MenuBar should be on the main view being edited"); - RemoveMenuItemOperation cmd = new RemoveMenuItemOperation(mi); ClassicAssert.IsTrue(cmd.Do()); From c2ea14cd5a94b815c7a73503ef5be7dd94bc3ed4 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:03:04 -0700 Subject: [PATCH 23/55] Test the operation itself is ok --- tests/MenuBarTests.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index d1070bc4..fdd66e8a 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -163,8 +163,13 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar() MenuItem mi = bar.Menus[ 0 ].Children[ 0 ]; - RemoveMenuItemOperation cmd = new RemoveMenuItemOperation(mi); - ClassicAssert.IsTrue(cmd.Do()); + RemoveMenuItemOperation? removeMenuItemOperation = null; + Assert.That( ( ) => removeMenuItemOperation = new( mi ), Throws.Nothing ); + Assert.That( removeMenuItemOperation, Is.Not.Null.And.InstanceOf( ) ); + + bool removeMenuItemOperationSucceeded = false; + Assert.That( ( ) => removeMenuItemOperationSucceeded = removeMenuItemOperation!.Do( ), Throws.Nothing ); + Assert.That( removeMenuItemOperationSucceeded ); ClassicAssert.IsEmpty(bar.Menus, "Expected menu bar header (File) to be removed along with it's last (only) child"); @@ -172,7 +177,7 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar() root.View.Subviews.Contains(bar), "Now that the MenuBar is completely empty it should be automatically removed"); - cmd.Undo(); + removeMenuItemOperation.Undo(); ClassicAssert.Contains(bar, root.View.Subviews.ToArray(), "Undo should put the MenuBar back on the view again"); From 5fb09155551f13e8fc22f09ced576675fbbf3791 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:07:54 -0700 Subject: [PATCH 24/55] Convert and simplify conditions --- tests/MenuBarTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index fdd66e8a..14b75ed7 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -171,11 +171,11 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar() Assert.That( ( ) => removeMenuItemOperationSucceeded = removeMenuItemOperation!.Do( ), Throws.Nothing ); Assert.That( removeMenuItemOperationSucceeded ); - ClassicAssert.IsEmpty(bar.Menus, "Expected menu bar header (File) to be removed along with it's last (only) child"); - - ClassicAssert.IsFalse( - root.View.Subviews.Contains(bar), - "Now that the MenuBar is completely empty it should be automatically removed"); + Assert.Multiple( ( ) => + { + Assert.That( bar.Menus, Is.Empty ); + Assert.That( root.View.Subviews, Has.None.InstanceOf( ) ); + } ); removeMenuItemOperation.Undo(); From 494385dae1803f79ebdf78f419110a2807921456 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:11:48 -0700 Subject: [PATCH 25/55] Track UndoStack and RedoStack --- tests/MenuBarTests.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 14b75ed7..088919a8 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -160,6 +160,8 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar() Assume.That( bar.Menus[ 0 ], Is.Not.Null.And.InstanceOf( ) ); Assume.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); Assume.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + Assume.That( OperationManager.Instance.UndoStackSize, Is.Zero ); + Assume.That( OperationManager.Instance.RedoStackSize, Is.Zero ); MenuItem mi = bar.Menus[ 0 ].Children[ 0 ]; @@ -170,6 +172,11 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar() bool removeMenuItemOperationSucceeded = false; Assert.That( ( ) => removeMenuItemOperationSucceeded = removeMenuItemOperation!.Do( ), Throws.Nothing ); Assert.That( removeMenuItemOperationSucceeded ); + Assert.Multiple( static ( ) => + { + Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 1 ) ); + Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); + } ); Assert.Multiple( ( ) => { @@ -177,7 +184,12 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar() Assert.That( root.View.Subviews, Has.None.InstanceOf( ) ); } ); - removeMenuItemOperation.Undo(); + Assert.That( removeMenuItemOperation!.Undo, Throws.Nothing ); + Assert.Multiple( static ( ) => + { + Assert.That( OperationManager.Instance.UndoStackSize, Is.Zero ); + Assert.That( OperationManager.Instance.RedoStackSize, Is.EqualTo( 1 ) ); + } ); ClassicAssert.Contains(bar, root.View.Subviews.ToArray(), "Undo should put the MenuBar back on the view again"); From 29ce307c1ac9ab9912e8683df4903492587856f1 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:19:52 -0700 Subject: [PATCH 26/55] Finish up with the rest of the test Assert end is identical to start Also use the OperationManager so the state of the stacks can be tested --- tests/MenuBarTests.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 088919a8..b886f99e 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -148,7 +148,7 @@ public void RoundTrip_PreserveMenuItems_WithSubmenus( ) [Test] [TestOf( typeof( RemoveMenuItemOperation ) )] - public void DeletingLastMenuItem_ShouldRemoveWholeBar() + public void DeletingLastMenuItem_ShouldRemoveWholeBar( ) { MenuBar bar = this.GetMenuBar( out Design root ); Assume.That( bar, Is.Not.Null.And.InstanceOf( ) ); @@ -170,7 +170,7 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar() Assert.That( removeMenuItemOperation, Is.Not.Null.And.InstanceOf( ) ); bool removeMenuItemOperationSucceeded = false; - Assert.That( ( ) => removeMenuItemOperationSucceeded = removeMenuItemOperation!.Do( ), Throws.Nothing ); + Assert.That( ( ) => removeMenuItemOperationSucceeded = OperationManager.Instance.Do( removeMenuItemOperation! ), Throws.Nothing ); Assert.That( removeMenuItemOperationSucceeded ); Assert.Multiple( static ( ) => { @@ -184,15 +184,24 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar() Assert.That( root.View.Subviews, Has.None.InstanceOf( ) ); } ); - Assert.That( removeMenuItemOperation!.Undo, Throws.Nothing ); + Assert.That( OperationManager.Instance.Undo, Throws.Nothing ); Assert.Multiple( static ( ) => { Assert.That( OperationManager.Instance.UndoStackSize, Is.Zero ); Assert.That( OperationManager.Instance.RedoStackSize, Is.EqualTo( 1 ) ); } ); - ClassicAssert.Contains(bar, root.View.Subviews.ToArray(), - "Undo should put the MenuBar back on the view again"); + // Same conditions as at the start + // The MenuBar should be back in the root view... + Assert.That( root.View.Subviews, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( root.View.Subviews[ 0 ], Is.Not.Null.And.SameAs( bar ) ); + + // ...And the original MenuBar should be back as it was at the start. + Assert.That( bar.Menus, Is.Not.Null ); + Assert.That( bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); } [Test] From 879db743df0c467fac84ec131d8eca83c39b0ac6 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:20:50 -0700 Subject: [PATCH 27/55] Annotate tested type --- tests/MenuBarTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index b886f99e..a67ecccb 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -205,7 +205,8 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar( ) } [Test] - public void TestDeletingMenuItemFromSubmenu_AllSubmenuChild() + [TestOf( typeof( RemoveMenuItemOperation ) )] + public void DeletingMenuItemFromSubmenu_AllSubmenuChild() { var bar = this.GetMenuBarWithSubmenuItems(out var head2, out var topChild); var bottomChild = head2.Children[1]; From 44bd86f66cd6c0593ee85607acd9021d366ddae7 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:24:23 -0700 Subject: [PATCH 28/55] Add disposal for this MenuBar and add a note for later --- tests/MenuBarTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index a67ecccb..2aabdde5 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -150,7 +150,7 @@ public void RoundTrip_PreserveMenuItems_WithSubmenus( ) [TestOf( typeof( RemoveMenuItemOperation ) )] public void DeletingLastMenuItem_ShouldRemoveWholeBar( ) { - MenuBar bar = this.GetMenuBar( out Design root ); + using MenuBar bar = this.GetMenuBar( out Design root ); Assume.That( bar, Is.Not.Null.And.InstanceOf( ) ); Assume.That( root, Is.Not.Null.And.InstanceOf( ) ); Assume.That( root.View.Subviews, Has.Exactly( 1 ).InstanceOf( ) ); @@ -189,6 +189,7 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar( ) { Assert.That( OperationManager.Instance.UndoStackSize, Is.Zero ); Assert.That( OperationManager.Instance.RedoStackSize, Is.EqualTo( 1 ) ); + // TODO: This needs to clean up after itself in a safe fashion } ); // Same conditions as at the start From c89f5ad6ea47fbd96494d72dbcdabd107d4bc2b1 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:35:05 -0700 Subject: [PATCH 29/55] Refactoring this guy to be clearer (WIP) --- tests/MenuBarTests.cs | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 2aabdde5..c57617f2 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -617,9 +617,12 @@ private MenuBar GetMenuBar(out Design root) return bar; } - private MenuBar GetMenuBarWithSubmenuItems(out MenuBarItem head2, out MenuItem topChild) + private (MenuBar Bar, MenuBarItem Head2, MenuItem TopChild) GetMenuBarWithSubmenuItems() { - var bar = this.GetMenuBar(); + (MenuBar Bar, MenuBarItem Head2, MenuItem TopChild) toReturn = new( ) + { + Bar = GetMenuBar( ) + }; // Set up a menu like: /* @@ -629,31 +632,36 @@ private MenuBar GetMenuBarWithSubmenuItems(out MenuBarItem head2, out MenuItem t Head3 Child2 */ - var mi = bar.Menus[0].Children[0]; + var mi = toReturn.Bar.Menus[0].Children[0]; mi.Title = "Head1"; - bar.Menus[0].Children = new[] + toReturn.Bar.Menus[ 0 ].Children = new[] + { + toReturn.Bar.Menus[ 0 ].Children[ 0 ], + toReturn.Head2 = CreateHead2Item( ), + new MenuItem( "Head3", null, static ( ) => { } ), + }; + + return toReturn; + + MenuBarItem CreateHead2Item( ) { - bar.Menus[0].Children[0], - head2 = new MenuBarItem(new[] + return new( new[] { - topChild = new MenuItem("Child1", null, () => { }) + toReturn.TopChild = new( "Child1", null, static ( ) => { } ) { Data = "Child1", Shortcut = Key.J.WithCtrl.KeyCode, }, - new MenuItem("Child2", null, () => { }) + new MenuItem( "Child2", null, static ( ) => { } ) { Data = "Child2", Shortcut = Key.F.WithCtrl.KeyCode, - }, - }) + } + } ) { Title = "Head2", - }, - new MenuItem("Head3", null, () => { }), - }; - - return bar; + }; + } } } \ No newline at end of file From dd96623e5f25768a2b4e6740239f1ad7c4437db3 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:39:14 -0700 Subject: [PATCH 30/55] More refactoring to make this clearer Each part is a local function so it's all easier to visually parse now --- tests/MenuBarTests.cs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index c57617f2..c1dcff9c 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -648,20 +648,30 @@ MenuBarItem CreateHead2Item( ) { return new( new[] { - toReturn.TopChild = new( "Child1", null, static ( ) => { } ) + toReturn.TopChild = CreateHead2Child1Item( ), + CreateHead2Child2Item( ) + } ) + { + Title = "Head2", + }; + + static MenuItem CreateHead2Child1Item( ) + { + return new( "Child1", null, static ( ) => { } ) { Data = "Child1", Shortcut = Key.J.WithCtrl.KeyCode, - }, - new MenuItem( "Child2", null, static ( ) => { } ) + }; + } + + static MenuItem CreateHead2Child2Item( ) + { + return new MenuItem( "Child2", null, static ( ) => { } ) { Data = "Child2", Shortcut = Key.F.WithCtrl.KeyCode, - } - } ) - { - Title = "Head2", - }; + }; + } } } } \ No newline at end of file From 846edf471554629ad930f74e02d60f54d682a82b Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:41:12 -0700 Subject: [PATCH 31/55] There we go Collection expressions to the rescue! --- tests/MenuBarTests.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index c1dcff9c..aa95b0bf 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -635,22 +635,18 @@ Head3 Child2 var mi = toReturn.Bar.Menus[0].Children[0]; mi.Title = "Head1"; - toReturn.Bar.Menus[ 0 ].Children = new[] - { + toReturn.Bar.Menus[ 0 ].Children = + [ toReturn.Bar.Menus[ 0 ].Children[ 0 ], toReturn.Head2 = CreateHead2Item( ), - new MenuItem( "Head3", null, static ( ) => { } ), - }; + new ( "Head3", null, static ( ) => { } ), + ]; return toReturn; MenuBarItem CreateHead2Item( ) { - return new( new[] - { - toReturn.TopChild = CreateHead2Child1Item( ), - CreateHead2Child2Item( ) - } ) + return new( [ toReturn.TopChild = CreateHead2Child1Item( ), CreateHead2Child2Item( )] ) { Title = "Head2", }; @@ -666,7 +662,7 @@ static MenuItem CreateHead2Child1Item( ) static MenuItem CreateHead2Child2Item( ) { - return new MenuItem( "Child2", null, static ( ) => { } ) + return new ( "Child2", null, static ( ) => { } ) { Data = "Child2", Shortcut = Key.F.WithCtrl.KeyCode, From 8aa8234b7eccf0f4c2de8962ad792dc2c38d1b6c Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:43:50 -0700 Subject: [PATCH 32/55] And update the usages! (done) --- tests/MenuBarTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index aa95b0bf..a3b4b755 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -209,8 +209,8 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar( ) [TestOf( typeof( RemoveMenuItemOperation ) )] public void DeletingMenuItemFromSubmenu_AllSubmenuChild() { - var bar = this.GetMenuBarWithSubmenuItems(out var head2, out var topChild); - var bottomChild = head2.Children[1]; + ( MenuBar bar, MenuBarItem head2, MenuItem topChild ) = GetMenuBarWithSubmenuItems( ); + MenuItem? bottomChild = head2.Children[1]; ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); ClassicAssert.AreEqual(typeof(MenuBarItem), bar.Menus[0].Children[1].GetType()); @@ -247,7 +247,7 @@ public void DeletingMenuItemFromSubmenu_AllSubmenuChild() [Test] public void TestDeletingMenuItemFromSubmenu_TopChild() { - var bar = this.GetMenuBarWithSubmenuItems(out var head2, out var topChild); + ( MenuBar bar, MenuBarItem head2, MenuItem topChild ) = GetMenuBarWithSubmenuItems( ); ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); ClassicAssert.AreEqual(2, head2.Children.Length); @@ -478,7 +478,7 @@ public void TestMoveMenuItemLeft_CannotMoveRootItems() [Test] public void TestMoveMenuItemLeft_MoveTopChild() { - var bar = this.GetMenuBarWithSubmenuItems(out var head2, out var topChild); + ( MenuBar bar, MenuBarItem head2, MenuItem topChild ) = GetMenuBarWithSubmenuItems( ); ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); ClassicAssert.AreEqual(2, head2.Children.Length); From b64c9b1a91d5beb75c476efcd503ed75afde6798 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:50:09 -0700 Subject: [PATCH 33/55] These are actually pre-conditions --- tests/MenuBarTests.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index a3b4b755..501dd350 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -210,12 +210,13 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar( ) public void DeletingMenuItemFromSubmenu_AllSubmenuChild() { ( MenuBar bar, MenuBarItem head2, MenuItem topChild ) = GetMenuBarWithSubmenuItems( ); - MenuItem? bottomChild = head2.Children[1]; - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(typeof(MenuBarItem), bar.Menus[0].Children[1].GetType()); - ClassicAssert.AreEqual(2, head2.Children.Length); - ClassicAssert.AreSame(topChild, head2.Children[0]); + MenuItem? bottomChild = head2.Children[ 1 ]; + + Assume.That( bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assume.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + Assume.That( head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assume.That( head2.Children[ 0 ], Is.SameAs( topChild ) ); var cmd1 = new RemoveMenuItemOperation(topChild); ClassicAssert.IsTrue(cmd1.Do()); From 661a09116dd4f836930e6229d3c3737f58e86020 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 20:54:15 -0700 Subject: [PATCH 34/55] Be sure these don't throw --- tests/MenuBarTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 501dd350..6d1ab579 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -218,11 +218,11 @@ public void DeletingMenuItemFromSubmenu_AllSubmenuChild() Assume.That( head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); Assume.That( head2.Children[ 0 ], Is.SameAs( topChild ) ); - var cmd1 = new RemoveMenuItemOperation(topChild); - ClassicAssert.IsTrue(cmd1.Do()); + RemoveMenuItemOperation cmd1 = new (topChild); + Assert.That( cmd1.Do, Throws.Nothing ); - var cmd2 = new RemoveMenuItemOperation(bottomChild); - ClassicAssert.IsTrue(cmd2.Do()); + RemoveMenuItemOperation cmd2 = new (bottomChild); + Assert.That( cmd2.Do, Throws.Nothing ); // Deleting both children should convert us from // a dropdown submenu to just a regular MenuItem From c929629d9a3f7ec020941588d0cb48b1b6930d78 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 21:02:39 -0700 Subject: [PATCH 35/55] Finish converting and improving this test --- tests/MenuBarTests.cs | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 6d1ab579..740d022b 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -207,7 +207,7 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar( ) [Test] [TestOf( typeof( RemoveMenuItemOperation ) )] - public void DeletingMenuItemFromSubmenu_AllSubmenuChild() + public void DeletingMenuItemFromSubmenu_AllSubmenuChild( ) { ( MenuBar bar, MenuBarItem head2, MenuItem topChild ) = GetMenuBarWithSubmenuItems( ); @@ -218,31 +218,39 @@ public void DeletingMenuItemFromSubmenu_AllSubmenuChild() Assume.That( head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); Assume.That( head2.Children[ 0 ], Is.SameAs( topChild ) ); - RemoveMenuItemOperation cmd1 = new (topChild); + RemoveMenuItemOperation cmd1 = new( topChild ); Assert.That( cmd1.Do, Throws.Nothing ); - RemoveMenuItemOperation cmd2 = new (bottomChild); + RemoveMenuItemOperation cmd2 = new( bottomChild ); Assert.That( cmd2.Do, Throws.Nothing ); // Deleting both children should convert us from // a dropdown submenu to just a regular MenuItem - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(typeof(MenuItem), bar.Menus[0].Children[1].GetType()); + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); - cmd2.Undo(); + Assert.That( cmd2.Undo, Throws.Nothing ); // should bring the bottom one back - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(typeof(MenuBarItem), bar.Menus[0].Children[1].GetType()); - ClassicAssert.AreSame(bottomChild, ((MenuBarItem)bar.Menus[0].Children[1]).Children[0]); + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( ( (MenuBarItem)bar.Menus[ 0 ].Children[ 1 ] ).Children[ 0 ], Is.SameAs( bottomChild ) ); - cmd1.Undo(); + Assert.That( cmd1.Undo, Throws.Nothing ); // Both submenu items should now be back - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(typeof(MenuBarItem), bar.Menus[0].Children[1].GetType()); - ClassicAssert.AreSame(topChild, ((MenuBarItem)bar.Menus[0].Children[1]).Children[0]); - ClassicAssert.AreSame(bottomChild, ((MenuBarItem)bar.Menus[0].Children[1]).Children[1]); + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + } ); + Assert.Multiple( ( ) => + { + Assert.That( head2.Children[ 0 ], Is.SameAs( topChild ) ); + Assert.That( ( (MenuBarItem)bar.Menus[ 0 ].Children[ 1 ] ).Children[ 0 ], Is.SameAs( topChild ) ); + Assert.That( ( (MenuBarItem)bar.Menus[ 0 ].Children[ 1 ] ).Children[ 1 ], Is.SameAs( bottomChild ) ); + } ); } [Test] From 8cc404865fb635afe405b2535c3813994d53d6b4 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 21:21:21 -0700 Subject: [PATCH 36/55] Introduce a type so the menubars can be auto-disposed by usings --- tests/MenuBarTests.cs | 87 +++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 740d022b..142fcd78 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -209,16 +209,16 @@ public void DeletingLastMenuItem_ShouldRemoveWholeBar( ) [TestOf( typeof( RemoveMenuItemOperation ) )] public void DeletingMenuItemFromSubmenu_AllSubmenuChild( ) { - ( MenuBar bar, MenuBarItem head2, MenuItem topChild ) = GetMenuBarWithSubmenuItems( ); + using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); - MenuItem? bottomChild = head2.Children[ 1 ]; + MenuItem? bottomChild = m.Head2.Children[ 1 ]; - Assume.That( bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assume.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); - Assume.That( head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); - Assume.That( head2.Children[ 0 ], Is.SameAs( topChild ) ); + Assume.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assume.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + Assume.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assume.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); - RemoveMenuItemOperation cmd1 = new( topChild ); + RemoveMenuItemOperation cmd1 = new( m.TopChild ); Assert.That( cmd1.Do, Throws.Nothing ); RemoveMenuItemOperation cmd2 = new( bottomChild ); @@ -226,64 +226,64 @@ public void DeletingMenuItemFromSubmenu_AllSubmenuChild( ) // Deleting both children should convert us from // a dropdown submenu to just a regular MenuItem - Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); Assert.That( cmd2.Undo, Throws.Nothing ); // should bring the bottom one back - Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); - Assert.That( ( (MenuBarItem)bar.Menus[ 0 ].Children[ 1 ] ).Children[ 0 ], Is.SameAs( bottomChild ) ); + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( ( (MenuBarItem)m.Bar.Menus[ 0 ].Children[ 1 ] ).Children[ 0 ], Is.SameAs( bottomChild ) ); Assert.That( cmd1.Undo, Throws.Nothing ); // Both submenu items should now be back - Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); Assert.Multiple( ( ) => { - Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); - Assert.That( head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); } ); Assert.Multiple( ( ) => { - Assert.That( head2.Children[ 0 ], Is.SameAs( topChild ) ); - Assert.That( ( (MenuBarItem)bar.Menus[ 0 ].Children[ 1 ] ).Children[ 0 ], Is.SameAs( topChild ) ); - Assert.That( ( (MenuBarItem)bar.Menus[ 0 ].Children[ 1 ] ).Children[ 1 ], Is.SameAs( bottomChild ) ); + Assert.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); + Assert.That( ( (MenuBarItem)m.Bar.Menus[ 0 ].Children[ 1 ] ).Children[ 0 ], Is.SameAs( m.TopChild ) ); + Assert.That( ( (MenuBarItem)m.Bar.Menus[ 0 ].Children[ 1 ] ).Children[ 1 ], Is.SameAs( bottomChild ) ); } ); } [Test] - public void TestDeletingMenuItemFromSubmenu_TopChild() + public void TestDeletingMenuItemFromSubmenu_TopChild( ) { - ( MenuBar bar, MenuBarItem head2, MenuItem topChild ) = GetMenuBarWithSubmenuItems( ); + using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(2, head2.Children.Length); - ClassicAssert.AreSame(topChild, head2.Children[0]); + ClassicAssert.AreEqual( 3, m.Bar.Menus[ 0 ].Children.Length ); + ClassicAssert.AreEqual( 2, m.Head2.Children.Length ); + ClassicAssert.AreSame( m.TopChild, m.Head2.Children[ 0 ] ); - var cmd = new RemoveMenuItemOperation(topChild); - ClassicAssert.IsTrue(cmd.Do()); + var cmd = new RemoveMenuItemOperation( m.TopChild ); + ClassicAssert.IsTrue( cmd.Do( ) ); // Delete the top child should leave only 1 in submenu - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(1, head2.Children.Length); - ClassicAssert.AreNotSame(topChild, head2.Children[0]); + ClassicAssert.AreEqual( 3, m.Bar.Menus[ 0 ].Children.Length ); + ClassicAssert.AreEqual( 1, m.Head2.Children.Length ); + ClassicAssert.AreNotSame( m.TopChild, m.Head2.Children[ 0 ] ); - cmd.Undo(); + cmd.Undo( ); // should come back now - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(2, head2.Children.Length); - ClassicAssert.AreSame(topChild, head2.Children[0]); + ClassicAssert.AreEqual( 3, m.Bar.Menus[ 0 ].Children.Length ); + ClassicAssert.AreEqual( 2, m.Head2.Children.Length ); + ClassicAssert.AreSame( m.TopChild, m.Head2.Children[ 0 ] ); } [Test] [TestOf( typeof( MenuTracker ) )] // TODO: Break this one up into smaller units at some point. - public void TestMenuOperations() + public void TestMenuOperations( ) { - ViewToCode viewToCode = new (); + ViewToCode viewToCode = new( ); FileInfo file = new( $"{nameof( TestMenuOperations )}.cs" ); Design designOut = viewToCode.GenerateNewView( file, "YourNamespace", typeof( Dialog ) ); @@ -626,9 +626,9 @@ private MenuBar GetMenuBar(out Design root) return bar; } - private (MenuBar Bar, MenuBarItem Head2, MenuItem TopChild) GetMenuBarWithSubmenuItems() + private MenuBarWithSubmenuItems GetMenuBarWithSubmenuItems( ) { - (MenuBar Bar, MenuBarItem Head2, MenuItem TopChild) toReturn = new( ) + MenuBarWithSubmenuItems toReturn = new( GetMenuBar( ), null!, null! ) { Bar = GetMenuBar( ) }; @@ -671,7 +671,7 @@ static MenuItem CreateHead2Child1Item( ) static MenuItem CreateHead2Child2Item( ) { - return new ( "Child2", null, static ( ) => { } ) + return new( "Child2", null, static ( ) => { } ) { Data = "Child2", Shortcut = Key.F.WithCtrl.KeyCode, @@ -679,4 +679,17 @@ static MenuItem CreateHead2Child2Item( ) } } } + + internal record MenuBarWithSubmenuItems( MenuBar Bar, MenuBarItem Head2, MenuItem TopChild ) : IDisposable + { + /// + public void Dispose( ) + { + Bar.Dispose( ); + } + + public MenuBarItem Head2 { get; set; } = Head2; + public MenuItem TopChild { get; set; } = TopChild; + } + } \ No newline at end of file From cbda19b5b533ef4948961662b9f08f68827dcfac Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 21:23:14 -0700 Subject: [PATCH 37/55] Convert these and change to pre-conditions --- tests/MenuBarTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 142fcd78..bbf6d5c0 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -258,9 +258,9 @@ public void TestDeletingMenuItemFromSubmenu_TopChild( ) { using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); - ClassicAssert.AreEqual( 3, m.Bar.Menus[ 0 ].Children.Length ); - ClassicAssert.AreEqual( 2, m.Head2.Children.Length ); - ClassicAssert.AreSame( m.TopChild, m.Head2.Children[ 0 ] ); + Assume.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assume.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assume.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); var cmd = new RemoveMenuItemOperation( m.TopChild ); ClassicAssert.IsTrue( cmd.Do( ) ); From b810696208cfa6ad88ce2af475ba1cc07dc1d543 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 21:29:52 -0700 Subject: [PATCH 38/55] Finish converting and improving this test --- tests/MenuBarTests.cs | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index bbf6d5c0..bb8bc0bf 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -254,7 +254,8 @@ public void DeletingMenuItemFromSubmenu_AllSubmenuChild( ) } [Test] - public void TestDeletingMenuItemFromSubmenu_TopChild( ) + [TestOf( typeof( RemoveMenuItemOperation ) )] + public void DeletingMenuItemFromSubmenu_TopChild( ) { using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); @@ -262,20 +263,28 @@ public void TestDeletingMenuItemFromSubmenu_TopChild( ) Assume.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); Assume.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); - var cmd = new RemoveMenuItemOperation( m.TopChild ); - ClassicAssert.IsTrue( cmd.Do( ) ); + RemoveMenuItemOperation cmd = new ( m.TopChild ); + bool cmdSucceeded = false; + Assert.That( ( ) => cmdSucceeded = cmd.Do( ), Throws.Nothing ); + Assert.That( cmdSucceeded ); // Delete the top child should leave only 1 in submenu - ClassicAssert.AreEqual( 3, m.Bar.Menus[ 0 ].Children.Length ); - ClassicAssert.AreEqual( 1, m.Head2.Children.Length ); - ClassicAssert.AreNotSame( m.TopChild, m.Head2.Children[ 0 ] ); + Assert.Multiple( ( ) => + { + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( m.Head2.Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( m.Head2.Children[ 0 ], Is.Not.SameAs( m.TopChild ) ); + } ); - cmd.Undo( ); + Assert.That( cmd.Undo, Throws.Nothing ); - // should come back now - ClassicAssert.AreEqual( 3, m.Bar.Menus[ 0 ].Children.Length ); - ClassicAssert.AreEqual( 2, m.Head2.Children.Length ); - ClassicAssert.AreSame( m.TopChild, m.Head2.Children[ 0 ] ); + Assert.Multiple( ( ) => + { + // should come back now + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); + } ); } [Test] From c8cb5f7797b8f856cc9e30903a74f2163a576cec Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 21:35:19 -0700 Subject: [PATCH 39/55] Convert MoveMenuItemLeft_CannotMoveRootItems --- tests/MenuBarTests.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index bb8bc0bf..ddb2c3cb 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -481,16 +481,20 @@ public void TestMenuOperations( ) } [Test] - public void TestMoveMenuItemLeft_CannotMoveRootItems() + [TestOf( typeof( MoveMenuItemLeftOperation ) )] + public void MoveMenuItemLeft_CannotMoveRootItems( ) { - var bar = this.GetMenuBar(); + using MenuBar bar = GetMenuBar( ); - var mi = bar.Menus[0].Children[0]; + MenuItem? mi = bar.Menus[ 0 ].Children[ 0 ]; + Assume.That( mi, Is.Not.Null.And.InstanceOf( ) ); // cannot move a root item - ClassicAssert.IsFalse(new MoveMenuItemLeftOperation( - bar.Menus[0].Children[0]) - .Do()); + MoveMenuItemLeftOperation moveMenuItemLeftOperation = new( bar.Menus[ 0 ].Children[ 0 ] ); + Assert.That( moveMenuItemLeftOperation.IsImpossible ); + bool moveMenuItemLeftOperationSucceeded = false; + Assert.That( ( ) => moveMenuItemLeftOperationSucceeded = moveMenuItemLeftOperation.Do( ), Throws.Nothing ); + Assert.That( moveMenuItemLeftOperationSucceeded, Is.False ); } [Test] From 1aec46663e276aa1cc7fdbf0565313a5b74457e7 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 21:38:37 -0700 Subject: [PATCH 40/55] Convert these to pre-conditions --- tests/MenuBarTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index ddb2c3cb..c94131e6 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -498,13 +498,13 @@ public void MoveMenuItemLeft_CannotMoveRootItems( ) } [Test] - public void TestMoveMenuItemLeft_MoveTopChild() + public void MoveMenuItemLeft_MoveTopChild( ) { - ( MenuBar bar, MenuBarItem head2, MenuItem topChild ) = GetMenuBarWithSubmenuItems( ); + using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(2, head2.Children.Length); - ClassicAssert.AreSame(topChild, head2.Children[0]); + Assume.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assume.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assume.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); var cmd = new MoveMenuItemLeftOperation(topChild); ClassicAssert.IsTrue(cmd.Do()); From 5625d36943becc33188674286116995a4bee1bb3 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 21:40:31 -0700 Subject: [PATCH 41/55] Make sure nothing throws and IsImpossible is false --- tests/MenuBarTests.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index c94131e6..c8623091 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -506,8 +506,11 @@ public void MoveMenuItemLeft_MoveTopChild( ) Assume.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); Assume.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); - var cmd = new MoveMenuItemLeftOperation(topChild); - ClassicAssert.IsTrue(cmd.Do()); + MoveMenuItemLeftOperation moveMenuItemLeftOperation = new ( m.TopChild ); + Assert.That( moveMenuItemLeftOperation.IsImpossible, Is.False ); + bool moveMenuItemLeftOperationSucceeded = false; + Assert.That( ( ) => moveMenuItemLeftOperationSucceeded = moveMenuItemLeftOperation.Do( ), Throws.Nothing ); + Assert.That( moveMenuItemLeftOperationSucceeded ); // move the top child left should pull // it out of the submenu and onto the root From 277eb2b646eddf1d85f31297021dfce1748fbe1e Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 21:42:59 -0700 Subject: [PATCH 42/55] Convert and add a reference check --- tests/MenuBarTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index c8623091..c4c46755 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -514,8 +514,9 @@ public void MoveMenuItemLeft_MoveTopChild( ) // move the top child left should pull // it out of the submenu and onto the root - ClassicAssert.AreEqual(4, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(1, head2.Children.Length); + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 4 ).InstanceOf( ) ); + Assert.That( m.Head2.Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( m.Head2.Children[ 0 ], Is.Not.SameAs( m.TopChild ) ); // it should be pulled out underneath its parent // and preserve its (Name) and Shortcuts From f632cb932a85032bec9a420ca5ef764d803b721d Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 21:44:40 -0700 Subject: [PATCH 43/55] Convert and simplify This was already a reference equality check. If the SameAs constraint is true, the other three MUST be true --- tests/MenuBarTests.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index c4c46755..13946c56 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -519,11 +519,7 @@ public void MoveMenuItemLeft_MoveTopChild( ) Assert.That( m.Head2.Children[ 0 ], Is.Not.SameAs( m.TopChild ) ); // it should be pulled out underneath its parent - // and preserve its (Name) and Shortcuts - ClassicAssert.AreEqual(topChild.Title, bar.Menus[0].Children[2].Title); - ClassicAssert.AreEqual(topChild.Data, bar.Menus[0].Children[2].Data); - ClassicAssert.AreEqual(topChild.Shortcut, bar.Menus[0].Children[2].Shortcut); - ClassicAssert.AreSame(topChild, bar.Menus[0].Children[2]); + Assert.That( m.Bar.Menus[ 0 ].Children[ 2 ], Is.SameAs( m.TopChild ) ); // undoing command should return us to // previous state From 240dac42b264dee77584d62781b23a96f562ab6d Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 21:46:58 -0700 Subject: [PATCH 44/55] Convert and simplify the rest of this test As with the previous commit, the value checks are redundant to the reference check --- tests/MenuBarTests.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 13946c56..f2a5d60f 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -521,17 +521,12 @@ public void MoveMenuItemLeft_MoveTopChild( ) // it should be pulled out underneath its parent Assert.That( m.Bar.Menus[ 0 ].Children[ 2 ], Is.SameAs( m.TopChild ) ); - // undoing command should return us to - // previous state - cmd.Undo(); - - ClassicAssert.AreEqual(3, bar.Menus[0].Children.Length); - ClassicAssert.AreEqual(2, head2.Children.Length); + // undoing command should return us to previous state + Assert.That( moveMenuItemLeftOperation.Undo, Throws.Nothing ); - ClassicAssert.AreEqual(topChild.Title, head2.Children[0].Title); - ClassicAssert.AreEqual(topChild.Data, head2.Children[0].Data); - ClassicAssert.AreEqual(topChild.Shortcut, head2.Children[0].Shortcut); - ClassicAssert.AreSame(topChild, head2.Children[0]); + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); } [Test] From 97c5722e1055f41d3abff21f6124c87b2238d74d Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 22:15:47 -0700 Subject: [PATCH 45/55] Convert and upgrade this test. A lot more reference checking, since it is known to be a destructive operation, even through Undo. --- tests/MenuBarTests.cs | 95 +++++++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index f2a5d60f..fbc3d390 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -530,46 +530,91 @@ public void MoveMenuItemLeft_MoveTopChild( ) } [Test] - public void TestMoveMenuItemRight_CannotMoveElementZero() + [TestOf( typeof( AddMenuItemOperation ) )] + public void TestMoveMenuItemRight_CannotMoveElementZero( ) { - var bar = this.GetMenuBar(); + using MenuBar bar = GetMenuBar( ); - var mi = bar.Menus[0].Children[0]; + MenuItem? mi = bar.Menus[ 0 ].Children[ 0 ]; mi.Data = "yarg"; mi.Shortcut = Key.Y.WithCtrl.KeyCode; - var addAnother = new AddMenuItemOperation(mi); - ClassicAssert.True(addAnother.Do()); + + AddMenuItemOperation addAnother = new( mi ); + Assert.That( addAnother.IsImpossible, Is.False ); + bool addAnotherSucceeded = false; + Assert.That( ( ) => addAnotherSucceeded = addAnother.Do( ), Throws.Nothing ); + Assert.That( addAnotherSucceeded ); // should have added below us - ClassicAssert.AreSame(mi, bar.Menus[0].Children[0]); - ClassicAssert.AreNotSame(mi, bar.Menus[0].Children[1]); - ClassicAssert.AreEqual(2, bar.Menus[0].Children.Length); + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.SameAs( mi ) ); + Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.SameAs( mi ) ); + } ); // cannot move element 0 - ClassicAssert.IsFalse(new MoveMenuItemRightOperation( - bar.Menus[0].Children[0]) - .Do()); - - var cmd = new MoveMenuItemRightOperation( - bar.Menus[0].Children[1]); + MoveMenuItemRightOperation impossibleMoveRightOp = new( bar.Menus[ 0 ].Children[ 0 ] ); + Assert.That( impossibleMoveRightOp.IsImpossible ); + bool impossibleMoveRightOpSucceeded = false; + Assert.That( ( ) => impossibleMoveRightOpSucceeded = impossibleMoveRightOp.Do( ), Throws.Nothing ); + Assert.That( impossibleMoveRightOpSucceeded, Is.False ); // can move element 1 - ClassicAssert.IsTrue(cmd.Do()); + // This is a destructive action, so references will change. + MoveMenuItemRightOperation validMoveRightOp = new( bar.Menus[ 0 ].Children[ 1 ] ); + Assert.That( validMoveRightOp.IsImpossible, Is.False ); + bool validMoveRightOpSucceeded = false; + Assert.That( ( ) => validMoveRightOpSucceeded = validMoveRightOp.Do( ), Throws.Nothing ); + Assert.That( validMoveRightOpSucceeded ); // We will have changed from a MenuItem to a MenuBarItem // so element 0 will not be us. In Terminal.Gui there is - // a different class for a menu item and one with submenus - ClassicAssert.AreNotSame(mi, bar.Menus[0].Children[0]); - ClassicAssert.AreEqual(mi.Title, bar.Menus[0].Children[0].Title); - ClassicAssert.AreEqual(mi.Data, bar.Menus[0].Children[0].Data); - ClassicAssert.AreEqual(1, bar.Menus[0].Children.Length); + // a different class for a menu item and one with submenus. + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + MenuBarItem miConvertedToMenuBarItem = (MenuBarItem)bar.Menus[ 0 ].Children[ 0 ]; - cmd.Undo(); + // Check that the references are unequal but values are equal + Assert.Multiple( ( ) => + { + Assert.That( miConvertedToMenuBarItem, Is.Not.SameAs( mi ) ); + Assert.That( miConvertedToMenuBarItem.Title, Is.EqualTo( mi.Title ) ); + Assert.That( miConvertedToMenuBarItem.Data, Is.EqualTo( mi.Data ) ); + Assert.That( miConvertedToMenuBarItem.Children, Has.Exactly( 1 ).InstanceOf( ) ); + } ); - ClassicAssert.AreEqual(mi.Title, bar.Menus[0].Children[0].Title); - ClassicAssert.AreEqual(mi.Data, bar.Menus[0].Children[0].Data); - ClassicAssert.AreEqual(mi.Shortcut, bar.Menus[0].Children[0].Shortcut); - ClassicAssert.AreNotSame(mi, bar.Menus[0].Children[1]); + // Now undo it. + // This is destructive as well. + Assert.That( validMoveRightOp.Undo, Throws.Nothing ); + + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + } ); + MenuItem firstChildAfterUndo = bar.Menus[ 0 ].Children[ 0 ]; + MenuItem secondChildAfterUndo = bar.Menus[ 0 ].Children[ 1 ]; + + Assert.Multiple( ( ) => + { + // All the previous references are gone forever through this process. + // So, neither element should be mi. + Assert.That( firstChildAfterUndo, Is.Not.SameAs( mi ) ); + Assert.That( secondChildAfterUndo, Is.Not.SameAs( mi ) ); + + // Neither element should be miConvertedToMenuBarItem either + Assert.That( firstChildAfterUndo, Is.Not.SameAs( miConvertedToMenuBarItem ) ); + Assert.That( secondChildAfterUndo, Is.Not.SameAs( miConvertedToMenuBarItem ) ); + + // And mi still should not be miConvertedToMenuBarItem + Assert.That( mi, Is.Not.SameAs( miConvertedToMenuBarItem ) ); + + // But the values need to be preserved + Assert.That( firstChildAfterUndo.Title, Is.EqualTo( mi.Title ) ); + Assert.That( firstChildAfterUndo.Data, Is.EqualTo( mi.Data ) ); + Assert.That( firstChildAfterUndo.Shortcut, Is.EqualTo( mi.Shortcut ) ); + } ); } /// From 2fc019ec2c87c600af014677eed7b7684f520459 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 22:18:36 -0700 Subject: [PATCH 46/55] Convert this test Yay! A small one! --- tests/MenuBarTests.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index fbc3d390..23cbb803 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -622,13 +622,15 @@ public void TestMoveMenuItemRight_CannotMoveElementZero( ) /// that it cannot be moved into a submenu /// [Test] - public void TestMoveMenuItemRight_CannotMoveLast() + [TestOf( typeof( MoveMenuItemRightOperation ) )] + public void MoveMenuItemRight_CannotMoveLast( ) { - var bar = this.GetMenuBar(); + MenuBar bar = GetMenuBar( ); - var mi = bar.Menus[0].Children[0]; - var cmd = new MoveMenuItemRightOperation(mi); - ClassicAssert.IsFalse(cmd.Do()); + MenuItem? mi = bar.Menus[ 0 ].Children[ 0 ]; + MoveMenuItemRightOperation cmd = new( mi ); + Assert.That( cmd.IsImpossible ); + Assert.That( cmd.Do, Is.False ); } /// From 7c64feebecb06f6af74d86b4eaaaadc1d6f3064c Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 22:56:57 -0700 Subject: [PATCH 47/55] Add tests to ensure the helpers are valid and to reduce redundancy --- tests/MenuBarTests.cs | 84 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 23cbb803..ba65d96b 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -672,16 +672,88 @@ private MenuBar GetMenuBar(out Design root) var bar = ViewFactory.Create( ); var addBarCmd = new AddViewOperation(bar, root, "mb"); ClassicAssert.IsTrue(addBarCmd.Do()); + [Test] + [TestOf( typeof( MenuBarTests ) )] + [Category( "Change Control" )] + [Order( 1 )] + [Repeat( 10 )] + [Description( "Ensures that the GetMenuBar helper method returns the expected objects and doesn't fail even if used multiple times." )] + public void GetMenuBar_BehavesAsExpected( ) + { + using MenuBar bar = GetMenuBar( out Design root ); + Assert.Multiple( ( ) => + { + Assert.That( bar, Is.Not.Null.And.InstanceOf( ) ); + Assert.That( root, Is.Not.Null.And.InstanceOf( ) ); + } ); + Assert.That( root.View.Subviews, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( root.View.Subviews[ 0 ], Is.Not.Null.And.SameAs( bar ) ); + Assert.That( bar.Menus, Is.Not.Null ); + } ); + Assert.That( bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( OperationManager.Instance.UndoStackSize, Is.Zero ); + Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); + } ); + } - // Expect ViewFactory to have created a single - // placeholder menu item - ClassicAssert.AreEqual(1, bar.Menus.Length); - ClassicAssert.AreEqual(1, bar.Menus[0].Children.Length); + [Test] + [TestOf( typeof( MenuBarWithSubmenuItems ) )] + [Category( "Change Control" )] + [Order( 2 )] + [Repeat( 10 )] + [Description( "Ensures that the GetMenuBarWithSubmenuItems helper method returns the expected objects and doesn't fail even if used multiple times." )] + public void GetMenuBarWithSubmenuItems_BehavesAsExpected( ) + { + using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); - return bar; + Assert.That( m.Bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); + + MenuBarItem menu0 = m.Bar.Menus[ 0 ]; + Assert.That( menu0.Children, Has.Exactly( 3 ).InstanceOf( ) ); + + // First item + MenuItem menu0Child0 = menu0.Children[ 0 ]; + Assert.That( menu0Child0.Title, Is.EqualTo( "Head1" ) ); + + // Second item and its children + Assert.That( menu0.Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + MenuBarItem menu0Child1 = (MenuBarItem)menu0.Children[ 1 ]; + Assert.Multiple( ( ) => + { + Assert.That( menu0Child1.Title, Is.EqualTo( "Head2" ) ); + Assert.That( menu0Child1.Children, Has.Exactly( 2 ).InstanceOf( ) ); + } ); + MenuItem menu0Child1Leaf0 = menu0Child1.Children[ 0 ]; + MenuItem menu0Child1Leaf1 = menu0Child1.Children[ 1 ]; + Assert.Multiple( ( ) => + { + Assert.That( menu0Child1Leaf0.Title, Is.EqualTo( "Child1" ) ); + Assert.That( menu0Child1Leaf0.Shortcut, Is.EqualTo( Key.J.WithCtrl.KeyCode ) ); + Assert.That( menu0Child1Leaf1.Title, Is.EqualTo( "Child2" ) ); + Assert.That( menu0Child1Leaf1.Shortcut, Is.EqualTo( Key.F.WithCtrl.KeyCode ) ); + } ); + + // Third item + Assert.That( menu0.Children[ 2 ], Is.Not.Null.And.InstanceOf( ) ); + MenuItem menu0Child2 = menu0.Children[ 2 ]; + Assert.That( menu0Child2.Title, Is.EqualTo( "Head3" ) ); + + //Now just make sure the record properties were set to the right references + Assert.Multiple( ( ) => + { + Assert.That( m.Head2, Is.SameAs( menu0Child1 ) ); + Assert.That( m.TopChild, Is.SameAs( menu0Child1Leaf0 ) ); + } ); } - private MenuBarWithSubmenuItems GetMenuBarWithSubmenuItems( ) + private static MenuBarWithSubmenuItems GetMenuBarWithSubmenuItems( ) { MenuBarWithSubmenuItems toReturn = new( GetMenuBar( ), null!, null! ) { From 0396cbc2d63ef15669607aa1d8a7ad5bc9027a34 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 22:57:45 -0700 Subject: [PATCH 48/55] Remove code that is now redundant --- tests/MenuBarTests.cs | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index ba65d96b..0162f3a4 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -150,18 +150,7 @@ public void RoundTrip_PreserveMenuItems_WithSubmenus( ) [TestOf( typeof( RemoveMenuItemOperation ) )] public void DeletingLastMenuItem_ShouldRemoveWholeBar( ) { - using MenuBar bar = this.GetMenuBar( out Design root ); - Assume.That( bar, Is.Not.Null.And.InstanceOf( ) ); - Assume.That( root, Is.Not.Null.And.InstanceOf( ) ); - Assume.That( root.View.Subviews, Has.Exactly( 1 ).InstanceOf( ) ); - Assume.That( root.View.Subviews[ 0 ], Is.Not.Null.And.SameAs( bar ) ); - Assume.That( bar.Menus, Is.Not.Null ); - Assume.That( bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); - Assume.That( bar.Menus[ 0 ], Is.Not.Null.And.InstanceOf( ) ); - Assume.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); - Assume.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); - Assume.That( OperationManager.Instance.UndoStackSize, Is.Zero ); - Assume.That( OperationManager.Instance.RedoStackSize, Is.Zero ); + using MenuBar bar = GetMenuBar( out Design root ); MenuItem mi = bar.Menus[ 0 ].Children[ 0 ]; @@ -486,9 +475,6 @@ public void MoveMenuItemLeft_CannotMoveRootItems( ) { using MenuBar bar = GetMenuBar( ); - MenuItem? mi = bar.Menus[ 0 ].Children[ 0 ]; - Assume.That( mi, Is.Not.Null.And.InstanceOf( ) ); - // cannot move a root item MoveMenuItemLeftOperation moveMenuItemLeftOperation = new( bar.Menus[ 0 ].Children[ 0 ] ); Assert.That( moveMenuItemLeftOperation.IsImpossible ); @@ -502,10 +488,6 @@ public void MoveMenuItemLeft_MoveTopChild( ) { using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); - Assume.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assume.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); - Assume.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); - MoveMenuItemLeftOperation moveMenuItemLeftOperation = new ( m.TopChild ); Assert.That( moveMenuItemLeftOperation.IsImpossible, Is.False ); bool moveMenuItemLeftOperationSucceeded = false; @@ -641,7 +623,7 @@ public void MoveMenuItemRight_CannotMoveLast( ) [Test] public void TestRemoveFinalMenuItemOnBar() { - var bar = this.GetMenuBar(); + var bar = GetMenuBar( ); var fileMenu = bar.Menus[0]; var placeholderMenuItem = fileMenu.Children[0]; @@ -660,18 +642,22 @@ public void TestRemoveFinalMenuItemOnBar() ClassicAssert.AreSame(placeholderMenuItem, bar.Menus[0].Children[0]); } - private MenuBar GetMenuBar() + private static MenuBar GetMenuBar( ) { - return this.GetMenuBar(out _); + return GetMenuBar( out _ ); } - private MenuBar GetMenuBar(out Design root) + private static MenuBar GetMenuBar( out Design root ) { - root = Get10By10View(); + root = Get10By10View( ); var bar = ViewFactory.Create( ); - var addBarCmd = new AddViewOperation(bar, root, "mb"); - ClassicAssert.IsTrue(addBarCmd.Do()); + var addBarCmd = new AddViewOperation( bar, root, "mb" ); + addBarCmd.Do( ); + + return bar; + } + [Test] [TestOf( typeof( MenuBarTests ) )] [Category( "Change Control" )] From cdc3020b5d53b2d2a7cb7876970f1cdb8f0a6152 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 22:58:52 -0700 Subject: [PATCH 49/55] Some more code that can go away with the new tests --- tests/MenuBarTests.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 0162f3a4..0d5fee1c 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -248,10 +248,6 @@ public void DeletingMenuItemFromSubmenu_TopChild( ) { using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); - Assume.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assume.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); - Assume.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); - RemoveMenuItemOperation cmd = new ( m.TopChild ); bool cmdSucceeded = false; Assert.That( ( ) => cmdSucceeded = cmd.Do( ), Throws.Nothing ); From f4b27b2a31a3c88a23c673072724e9be9a07e56b Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 23:08:20 -0700 Subject: [PATCH 50/55] Convert the last test method --- tests/MenuBarTests.cs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 0d5fee1c..24336082 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -617,25 +617,29 @@ public void MoveMenuItemRight_CannotMoveLast( ) /// should result in a completely empty menu bar and be undoable /// [Test] - public void TestRemoveFinalMenuItemOnBar() + [TestOf( typeof( RemoveMenuItemOperation ) )] + public void RemoveFinalMenuItemOnBar( ) { - var bar = GetMenuBar( ); + using MenuBar bar = GetMenuBar( ); - var fileMenu = bar.Menus[0]; - var placeholderMenuItem = fileMenu.Children[0]; + MenuBarItem? fileMenu = bar.Menus[ 0 ]; + MenuItem? placeholderMenuItem = fileMenu.Children[ 0 ]; - var remove = new RemoveMenuItemOperation(placeholderMenuItem); + RemoveMenuItemOperation removeOp = new( placeholderMenuItem ); // we are able to remove the last one - ClassicAssert.IsTrue(remove.Do()); - ClassicAssert.IsEmpty(bar.Menus, "menu bar should now be completely empty"); + Assert.That( removeOp.IsImpossible, Is.False ); + bool removeOpSucceeded = false; + Assert.That( ( ) => removeOpSucceeded = removeOp.Do( ), Throws.Nothing ); + Assert.That( removeOpSucceeded ); + Assert.That( bar.Menus, Is.Empty ); - remove.Undo(); + Assert.That( removeOp.Undo, Throws.Nothing ); // should be back to where we started - ClassicAssert.AreEqual(1, bar.Menus.Length); - ClassicAssert.AreEqual(1, bar.Menus[0].Children.Length); - ClassicAssert.AreSame(placeholderMenuItem, bar.Menus[0].Children[0]); + Assert.That( bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.SameAs( placeholderMenuItem ) ); } private static MenuBar GetMenuBar( ) From acdd5585218a3ed816113adc0035c0000d40eb72 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 23:09:10 -0700 Subject: [PATCH 51/55] Silence warnings from static analysis about possible null dereferences Too bad it isn't aware of the test methods that guaranteed not null already --- tests/MenuBarTests.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 24336082..d7832eca 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -31,7 +31,7 @@ public void RoundTrip_PreserveMenuItems() Assume.That( addViewOperation, Is.Not.Null.And.InstanceOf( ) ); bool addViewOperationSucceeded = false; - Assert.That( ( ) => addViewOperationSucceeded = OperationManager.Instance.Do( addViewOperation ), Throws.Nothing ); + Assert.That( ( ) => addViewOperationSucceeded = OperationManager.Instance.Do( addViewOperation! ), Throws.Nothing ); Assert.That( addViewOperationSucceeded ); Assume.That( ( ) => viewToCode.GenerateDesignerCs( designOut, typeof( Dialog ) ), Throws.Nothing ); @@ -41,19 +41,19 @@ public void RoundTrip_PreserveMenuItems() Assert.That( codeToView, Is.Not.Null.And.InstanceOf( ) ); Design? designBackIn = null; - Assert.That( ( ) => designBackIn = codeToView.CreateInstance( ), Throws.Nothing ); + Assert.That( ( ) => designBackIn = codeToView!.CreateInstance( ), Throws.Nothing ); Assert.That( designBackIn, Is.Not.Null.And.InstanceOf( ) ); // 1 visible root menu (e.g. File) MenuBar? mbIn = null; - Assert.That( designBackIn.View, Is.Not.Null.And.InstanceOf( ) ); + Assert.That( designBackIn!.View, Is.Not.Null.And.InstanceOf( ) ); IList actualSubviews = designBackIn.View.GetActualSubviews(); Assert.That( actualSubviews, Has.Exactly( 1 ).InstanceOf( ) ); Assert.That( ( ) => mbIn = actualSubviews.OfType( ).Single( ), Throws.Nothing ); Assert.That( mbIn, Is.Not.Null.And.InstanceOf( ) ); // 1 child menu item (e.g. Open) - Assert.That( mbIn.Menus, Is.Not.Null.And.Not.Empty ); + Assert.That( mbIn!.Menus, Is.Not.Null.And.Not.Empty ); Assert.That( mbIn.Menus, Has.Exactly( 1 ).InstanceOf( ) ); Assert.That( mbIn.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); Assert.That( mbIn.Menus[ 0 ].Children[ 0 ].Title, Is.EqualTo( mbOut.Menus[ 0 ].Children[ 0 ].Title ) ); @@ -91,11 +91,11 @@ public void RoundTrip_PreserveMenuItems_WithSubmenus( ) bool addChildMenuOperation1Succeeded = false; bool addChildMenuOperation2Succeeded = false; bool addChildMenuOperation3Succeeded = false; - Assert.That( ( ) => addChildMenuOperation1Succeeded = addChildMenuOperation1.Do( ), Throws.Nothing ); + Assert.That( ( ) => addChildMenuOperation1Succeeded = addChildMenuOperation1!.Do( ), Throws.Nothing ); Assert.That( addChildMenuOperation1Succeeded ); - Assert.That( ( ) => addChildMenuOperation2Succeeded = addChildMenuOperation2.Do( ), Throws.Nothing ); + Assert.That( ( ) => addChildMenuOperation2Succeeded = addChildMenuOperation2!.Do( ), Throws.Nothing ); Assert.That( addChildMenuOperation2Succeeded ); - Assert.That( ( ) => addChildMenuOperation3Succeeded = addChildMenuOperation3.Do( ), Throws.Nothing ); + Assert.That( ( ) => addChildMenuOperation3Succeeded = addChildMenuOperation3!.Do( ), Throws.Nothing ); Assert.That( addChildMenuOperation3Succeeded ); } ); @@ -122,7 +122,7 @@ public void RoundTrip_PreserveMenuItems_WithSubmenus( ) Design? designBackIn = null; - Assume.That( ( ) => designBackIn = codeToView.CreateInstance( ), Throws.Nothing ); + Assume.That( ( ) => designBackIn = codeToView!.CreateInstance( ), Throws.Nothing ); Assume.That( designBackIn, Is.Not.Null.And.InstanceOf( ) ); MenuBar? mbIn = null; @@ -134,7 +134,7 @@ public void RoundTrip_PreserveMenuItems_WithSubmenus( ) Assert.That( mbIn, Is.Not.Null.And.InstanceOf( ) ); // 1 visible root menu (e.g. File) - Assert.That( mbIn.Menus, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( mbIn!.Menus, Has.Exactly( 1 ).InstanceOf( ) ); // 3 child menu item (original one + 3 we added -1 because we moved it to submenu) Assert.That( mbIn.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); Assert.That( mbIn.Menus[ 0 ].Children, Has.All.Not.Null ); @@ -369,7 +369,7 @@ public void TestMenuOperations( ) } ); bool moveUpSucceeded = false; - Assert.That( ( ) => moveUpSucceeded = OperationManager.Instance.Do( up ), Throws.Nothing ); + Assert.That( ( ) => moveUpSucceeded = OperationManager.Instance.Do( up! ), Throws.Nothing ); Assert.That( moveUpSucceeded ); Assert.Multiple( ( ) => From e3d8b78df8571a310491068f7508fdfb15c729d7 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 23:09:34 -0700 Subject: [PATCH 52/55] These can all be static lambdas --- tests/MenuBarTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index d7832eca..240b1b3e 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -320,7 +320,7 @@ public void TestMenuOperations( ) Assert.That( mbOut.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.Not.SameAs( orig ) ); } ); - Assert.Multiple( ( ) => + Assert.Multiple( static ( ) => { Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 1 ) ); Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); @@ -328,7 +328,7 @@ public void TestMenuOperations( ) OperationManager.Instance.Undo(); - Assert.Multiple( ( ) => + Assert.Multiple( static ( ) => { Assert.That( OperationManager.Instance.UndoStackSize, Is.Zero ); Assert.That( OperationManager.Instance.RedoStackSize, Is.EqualTo( 1 ) ); @@ -340,7 +340,7 @@ public void TestMenuOperations( ) OperationManager.Instance.Redo(); - Assert.Multiple( ( ) => + Assert.Multiple( static ( ) => { Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 1 ) ); Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); @@ -372,7 +372,7 @@ public void TestMenuOperations( ) Assert.That( ( ) => moveUpSucceeded = OperationManager.Instance.Do( up! ), Throws.Nothing ); Assert.That( moveUpSucceeded ); - Assert.Multiple( ( ) => + Assert.Multiple( static ( ) => { Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 2 ) ); Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); @@ -420,7 +420,7 @@ public void TestMenuOperations( ) Assert.That( down, Is.Not.Null.And.InstanceOf( ) ); Assert.That( down!.IsImpossible, Is.False ); - Assert.Multiple( ( ) => + Assert.Multiple( static ( ) => { Assert.That( OperationManager.Instance.RedoStackSize, Is.EqualTo( 1 ) ); Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 1 ) ); @@ -429,7 +429,7 @@ public void TestMenuOperations( ) bool moveDownSucceeded = false; Assert.That( ( ) => moveDownSucceeded = OperationManager.Instance.Do( down ), Throws.Nothing ); Assert.That( moveDownSucceeded ); - Assert.Multiple( ( ) => + Assert.Multiple( static ( ) => { Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 2 ) ); From e3023a438b295ce378e919776aa36dae46155c16 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 23:10:46 -0700 Subject: [PATCH 53/55] Re-ordering code after member name changes and new additions (just moves) --- tests/MenuBarTests.cs | 667 +++++++++++++++++++++--------------------- 1 file changed, 333 insertions(+), 334 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 240b1b3e..0ee60567 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -7,6 +7,302 @@ namespace UnitTests; [Category( "UI" )] internal class MenuBarTests : Tests { + [Test] + [TestOf( typeof( RemoveMenuItemOperation ) )] + public void DeletingLastMenuItem_ShouldRemoveWholeBar( ) + { + using MenuBar bar = GetMenuBar( out Design root ); + + MenuItem mi = bar.Menus[ 0 ].Children[ 0 ]; + + RemoveMenuItemOperation? removeMenuItemOperation = null; + Assert.That( ( ) => removeMenuItemOperation = new( mi ), Throws.Nothing ); + Assert.That( removeMenuItemOperation, Is.Not.Null.And.InstanceOf( ) ); + + bool removeMenuItemOperationSucceeded = false; + Assert.That( ( ) => removeMenuItemOperationSucceeded = OperationManager.Instance.Do( removeMenuItemOperation! ), Throws.Nothing ); + Assert.That( removeMenuItemOperationSucceeded ); + Assert.Multiple( static ( ) => + { + Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 1 ) ); + Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); + } ); + + Assert.Multiple( ( ) => + { + Assert.That( bar.Menus, Is.Empty ); + Assert.That( root.View.Subviews, Has.None.InstanceOf( ) ); + } ); + + Assert.That( OperationManager.Instance.Undo, Throws.Nothing ); + Assert.Multiple( static ( ) => + { + Assert.That( OperationManager.Instance.UndoStackSize, Is.Zero ); + Assert.That( OperationManager.Instance.RedoStackSize, Is.EqualTo( 1 ) ); + // TODO: This needs to clean up after itself in a safe fashion + } ); + + // Same conditions as at the start + // The MenuBar should be back in the root view... + Assert.That( root.View.Subviews, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( root.View.Subviews[ 0 ], Is.Not.Null.And.SameAs( bar ) ); + + // ...And the original MenuBar should be back as it was at the start. + Assert.That( bar.Menus, Is.Not.Null ); + Assert.That( bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + } + + [Test] + [TestOf( typeof( RemoveMenuItemOperation ) )] + public void DeletingMenuItemFromSubmenu_AllSubmenuChild( ) + { + using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); + + MenuItem? bottomChild = m.Head2.Children[ 1 ]; + + Assume.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assume.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + Assume.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assume.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); + + RemoveMenuItemOperation cmd1 = new( m.TopChild ); + Assert.That( cmd1.Do, Throws.Nothing ); + + RemoveMenuItemOperation cmd2 = new( bottomChild ); + Assert.That( cmd2.Do, Throws.Nothing ); + + // Deleting both children should convert us from + // a dropdown submenu to just a regular MenuItem + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + + Assert.That( cmd2.Undo, Throws.Nothing ); + + // should bring the bottom one back + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( ( (MenuBarItem)m.Bar.Menus[ 0 ].Children[ 1 ] ).Children[ 0 ], Is.SameAs( bottomChild ) ); + + Assert.That( cmd1.Undo, Throws.Nothing ); + + // Both submenu items should now be back + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + } ); + Assert.Multiple( ( ) => + { + Assert.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); + Assert.That( ( (MenuBarItem)m.Bar.Menus[ 0 ].Children[ 1 ] ).Children[ 0 ], Is.SameAs( m.TopChild ) ); + Assert.That( ( (MenuBarItem)m.Bar.Menus[ 0 ].Children[ 1 ] ).Children[ 1 ], Is.SameAs( bottomChild ) ); + } ); + } + + [Test] + [TestOf( typeof( RemoveMenuItemOperation ) )] + public void DeletingMenuItemFromSubmenu_TopChild( ) + { + using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); + + RemoveMenuItemOperation cmd = new ( m.TopChild ); + bool cmdSucceeded = false; + Assert.That( ( ) => cmdSucceeded = cmd.Do( ), Throws.Nothing ); + Assert.That( cmdSucceeded ); + + // Delete the top child should leave only 1 in submenu + Assert.Multiple( ( ) => + { + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( m.Head2.Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( m.Head2.Children[ 0 ], Is.Not.SameAs( m.TopChild ) ); + } ); + + Assert.That( cmd.Undo, Throws.Nothing ); + + Assert.Multiple( ( ) => + { + // should come back now + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); + } ); + } + + [Test] + [TestOf( typeof( MenuBarTests ) )] + [Category( "Change Control" )] + [Order( 1 )] + [Repeat( 10 )] + [Description( "Ensures that the GetMenuBar helper method returns the expected objects and doesn't fail even if used multiple times." )] + public void GetMenuBar_BehavesAsExpected( ) + { + using MenuBar bar = GetMenuBar( out Design root ); + Assert.Multiple( ( ) => + { + Assert.That( bar, Is.Not.Null.And.InstanceOf( ) ); + Assert.That( root, Is.Not.Null.And.InstanceOf( ) ); + } ); + Assert.That( root.View.Subviews, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( root.View.Subviews[ 0 ], Is.Not.Null.And.SameAs( bar ) ); + Assert.That( bar.Menus, Is.Not.Null ); + } ); + Assert.That( bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( OperationManager.Instance.UndoStackSize, Is.Zero ); + Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); + } ); + } + + [Test] + [TestOf( typeof( MenuBarWithSubmenuItems ) )] + [Category( "Change Control" )] + [Order( 2 )] + [Repeat( 10 )] + [Description( "Ensures that the GetMenuBarWithSubmenuItems helper method returns the expected objects and doesn't fail even if used multiple times." )] + public void GetMenuBarWithSubmenuItems_BehavesAsExpected( ) + { + using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); + + Assert.That( m.Bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); + + MenuBarItem menu0 = m.Bar.Menus[ 0 ]; + Assert.That( menu0.Children, Has.Exactly( 3 ).InstanceOf( ) ); + + // First item + MenuItem menu0Child0 = menu0.Children[ 0 ]; + Assert.That( menu0Child0.Title, Is.EqualTo( "Head1" ) ); + + // Second item and its children + Assert.That( menu0.Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + MenuBarItem menu0Child1 = (MenuBarItem)menu0.Children[ 1 ]; + Assert.Multiple( ( ) => + { + Assert.That( menu0Child1.Title, Is.EqualTo( "Head2" ) ); + Assert.That( menu0Child1.Children, Has.Exactly( 2 ).InstanceOf( ) ); + } ); + MenuItem menu0Child1Leaf0 = menu0Child1.Children[ 0 ]; + MenuItem menu0Child1Leaf1 = menu0Child1.Children[ 1 ]; + Assert.Multiple( ( ) => + { + Assert.That( menu0Child1Leaf0.Title, Is.EqualTo( "Child1" ) ); + Assert.That( menu0Child1Leaf0.Shortcut, Is.EqualTo( Key.J.WithCtrl.KeyCode ) ); + Assert.That( menu0Child1Leaf1.Title, Is.EqualTo( "Child2" ) ); + Assert.That( menu0Child1Leaf1.Shortcut, Is.EqualTo( Key.F.WithCtrl.KeyCode ) ); + } ); + + // Third item + Assert.That( menu0.Children[ 2 ], Is.Not.Null.And.InstanceOf( ) ); + MenuItem menu0Child2 = menu0.Children[ 2 ]; + Assert.That( menu0Child2.Title, Is.EqualTo( "Head3" ) ); + + //Now just make sure the record properties were set to the right references + Assert.Multiple( ( ) => + { + Assert.That( m.Head2, Is.SameAs( menu0Child1 ) ); + Assert.That( m.TopChild, Is.SameAs( menu0Child1Leaf0 ) ); + } ); + } + + [Test] + [TestOf( typeof( MoveMenuItemLeftOperation ) )] + public void MoveMenuItemLeft_CannotMoveRootItems( ) + { + using MenuBar bar = GetMenuBar( ); + + // cannot move a root item + MoveMenuItemLeftOperation moveMenuItemLeftOperation = new( bar.Menus[ 0 ].Children[ 0 ] ); + Assert.That( moveMenuItemLeftOperation.IsImpossible ); + bool moveMenuItemLeftOperationSucceeded = false; + Assert.That( ( ) => moveMenuItemLeftOperationSucceeded = moveMenuItemLeftOperation.Do( ), Throws.Nothing ); + Assert.That( moveMenuItemLeftOperationSucceeded, Is.False ); + } + + [Test] + public void MoveMenuItemLeft_MoveTopChild( ) + { + using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); + + MoveMenuItemLeftOperation moveMenuItemLeftOperation = new ( m.TopChild ); + Assert.That( moveMenuItemLeftOperation.IsImpossible, Is.False ); + bool moveMenuItemLeftOperationSucceeded = false; + Assert.That( ( ) => moveMenuItemLeftOperationSucceeded = moveMenuItemLeftOperation.Do( ), Throws.Nothing ); + Assert.That( moveMenuItemLeftOperationSucceeded ); + + // move the top child left should pull + // it out of the submenu and onto the root + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 4 ).InstanceOf( ) ); + Assert.That( m.Head2.Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( m.Head2.Children[ 0 ], Is.Not.SameAs( m.TopChild ) ); + + // it should be pulled out underneath its parent + Assert.That( m.Bar.Menus[ 0 ].Children[ 2 ], Is.SameAs( m.TopChild ) ); + + // undoing command should return us to previous state + Assert.That( moveMenuItemLeftOperation.Undo, Throws.Nothing ); + + Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); + } + + /// + /// Tests that when there is only one menu item + /// that it cannot be moved into a submenu + /// + [Test] + [TestOf( typeof( MoveMenuItemRightOperation ) )] + public void MoveMenuItemRight_CannotMoveLast( ) + { + MenuBar bar = GetMenuBar( ); + + MenuItem? mi = bar.Menus[ 0 ].Children[ 0 ]; + MoveMenuItemRightOperation cmd = new( mi ); + Assert.That( cmd.IsImpossible ); + Assert.That( cmd.Do, Is.False ); + } + + /// + /// Tests removing the last menu item (i.e. 'Do Something') + /// under the only remaining menu header (e.g. 'File F9') + /// should result in a completely empty menu bar and be undoable + /// + [Test] + [TestOf( typeof( RemoveMenuItemOperation ) )] + public void RemoveFinalMenuItemOnBar( ) + { + using MenuBar bar = GetMenuBar( ); + + MenuBarItem? fileMenu = bar.Menus[ 0 ]; + MenuItem? placeholderMenuItem = fileMenu.Children[ 0 ]; + + RemoveMenuItemOperation removeOp = new( placeholderMenuItem ); + + // we are able to remove the last one + Assert.That( removeOp.IsImpossible, Is.False ); + bool removeOpSucceeded = false; + Assert.That( ( ) => removeOpSucceeded = removeOp.Do( ), Throws.Nothing ); + Assert.That( removeOpSucceeded ); + Assert.That( bar.Menus, Is.Empty ); + + Assert.That( removeOp.Undo, Throws.Nothing ); + + // should be back to where we started + Assert.That( bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.SameAs( placeholderMenuItem ) ); + } + [Test] public void RoundTrip_PreserveMenuItems() { @@ -103,173 +399,47 @@ public void RoundTrip_PreserveMenuItems_WithSubmenus( ) MoveMenuItemRightOperation moveMenuItemOperation = new( mbOut.Menus[ 0 ].Children[ 1 ] ); Assert.That( ( ) => moveMenuItemOperation.Do( ), Throws.Nothing ); - // 1 visible root menu (e.g. File) - Assert.That( mbOut.Menus, Has.Exactly( 1 ).InstanceOf( ) ); - - // 3 child menu item (original one + 3 we added -1 because we moved it to submenu) - Assert.That( mbOut.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - - // should be 1 submenu item (the one we moved) - Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.InstanceOf( ) ); - Assert.That( ( (MenuBarItem)mbOut.Menus[ 0 ].Children[ 0 ] ).Children, Has.Exactly( 1 ).InstanceOf( ) ); - - Assume.That( ( ) => viewToCode.GenerateDesignerCs( designOut, typeof( Dialog ) ), Throws.Nothing ); - Assume.That( designOut, Is.Not.Null.And.InstanceOf( ) ); - - CodeToView? codeToView = null; - Assume.That( ( ) => codeToView = new( designOut.SourceCode ), Throws.Nothing ); - Assume.That( codeToView, Is.Not.Null.And.InstanceOf( ) ); - - Design? designBackIn = null; - - Assume.That( ( ) => designBackIn = codeToView!.CreateInstance( ), Throws.Nothing ); - Assume.That( designBackIn, Is.Not.Null.And.InstanceOf( ) ); - - MenuBar? mbIn = null; - Assume.That( designBackIn!.View, Is.Not.Null.And.InstanceOf( ) ); - - IList actualSubviews = designBackIn.View.GetActualSubviews( ); - Assert.That( actualSubviews, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.That( ( ) => mbIn = actualSubviews.OfType( ).Single( ), Throws.Nothing ); - Assert.That( mbIn, Is.Not.Null.And.InstanceOf( ) ); - - // 1 visible root menu (e.g. File) - Assert.That( mbIn!.Menus, Has.Exactly( 1 ).InstanceOf( ) ); - // 3 child menu item (original one + 3 we added -1 because we moved it to submenu) - Assert.That( mbIn.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assert.That( mbIn.Menus[ 0 ].Children, Has.All.Not.Null ); - - // should be 1 submenu item (the one we moved) - Assert.That( ( (MenuBarItem)mbIn.Menus[ 0 ].Children[ 0 ] ).Children, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.That( - ( (MenuBarItem)mbIn.Menus[ 0 ].Children[ 0 ] ).Children[ 0 ].Title, - Is.EqualTo( ( (MenuBarItem)mbOut.Menus[ 0 ].Children[ 0 ] ).Children[ 0 ].Title ) ); - } - - [Test] - [TestOf( typeof( RemoveMenuItemOperation ) )] - public void DeletingLastMenuItem_ShouldRemoveWholeBar( ) - { - using MenuBar bar = GetMenuBar( out Design root ); - - MenuItem mi = bar.Menus[ 0 ].Children[ 0 ]; - - RemoveMenuItemOperation? removeMenuItemOperation = null; - Assert.That( ( ) => removeMenuItemOperation = new( mi ), Throws.Nothing ); - Assert.That( removeMenuItemOperation, Is.Not.Null.And.InstanceOf( ) ); - - bool removeMenuItemOperationSucceeded = false; - Assert.That( ( ) => removeMenuItemOperationSucceeded = OperationManager.Instance.Do( removeMenuItemOperation! ), Throws.Nothing ); - Assert.That( removeMenuItemOperationSucceeded ); - Assert.Multiple( static ( ) => - { - Assert.That( OperationManager.Instance.UndoStackSize, Is.EqualTo( 1 ) ); - Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); - } ); - - Assert.Multiple( ( ) => - { - Assert.That( bar.Menus, Is.Empty ); - Assert.That( root.View.Subviews, Has.None.InstanceOf( ) ); - } ); - - Assert.That( OperationManager.Instance.Undo, Throws.Nothing ); - Assert.Multiple( static ( ) => - { - Assert.That( OperationManager.Instance.UndoStackSize, Is.Zero ); - Assert.That( OperationManager.Instance.RedoStackSize, Is.EqualTo( 1 ) ); - // TODO: This needs to clean up after itself in a safe fashion - } ); - - // Same conditions as at the start - // The MenuBar should be back in the root view... - Assert.That( root.View.Subviews, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.That( root.View.Subviews[ 0 ], Is.Not.Null.And.SameAs( bar ) ); - - // ...And the original MenuBar should be back as it was at the start. - Assert.That( bar.Menus, Is.Not.Null ); - Assert.That( bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.That( bar.Menus[ 0 ], Is.Not.Null.And.InstanceOf( ) ); - Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); - } - - [Test] - [TestOf( typeof( RemoveMenuItemOperation ) )] - public void DeletingMenuItemFromSubmenu_AllSubmenuChild( ) - { - using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); - - MenuItem? bottomChild = m.Head2.Children[ 1 ]; - - Assume.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assume.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); - Assume.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); - Assume.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); - - RemoveMenuItemOperation cmd1 = new( m.TopChild ); - Assert.That( cmd1.Do, Throws.Nothing ); - - RemoveMenuItemOperation cmd2 = new( bottomChild ); - Assert.That( cmd2.Do, Throws.Nothing ); + // 1 visible root menu (e.g. File) + Assert.That( mbOut.Menus, Has.Exactly( 1 ).InstanceOf( ) ); - // Deleting both children should convert us from - // a dropdown submenu to just a regular MenuItem - Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assert.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + // 3 child menu item (original one + 3 we added -1 because we moved it to submenu) + Assert.That( mbOut.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assert.That( cmd2.Undo, Throws.Nothing ); + // should be 1 submenu item (the one we moved) + Assert.That( mbOut.Menus[ 0 ].Children[ 0 ], Is.InstanceOf( ) ); + Assert.That( ( (MenuBarItem)mbOut.Menus[ 0 ].Children[ 0 ] ).Children, Has.Exactly( 1 ).InstanceOf( ) ); - // should bring the bottom one back - Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assert.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); - Assert.That( ( (MenuBarItem)m.Bar.Menus[ 0 ].Children[ 1 ] ).Children[ 0 ], Is.SameAs( bottomChild ) ); + Assume.That( ( ) => viewToCode.GenerateDesignerCs( designOut, typeof( Dialog ) ), Throws.Nothing ); + Assume.That( designOut, Is.Not.Null.And.InstanceOf( ) ); - Assert.That( cmd1.Undo, Throws.Nothing ); + CodeToView? codeToView = null; + Assume.That( ( ) => codeToView = new( designOut.SourceCode ), Throws.Nothing ); + Assume.That( codeToView, Is.Not.Null.And.InstanceOf( ) ); - // Both submenu items should now be back - Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assert.Multiple( ( ) => - { - Assert.That( m.Bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); - Assert.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); - } ); - Assert.Multiple( ( ) => - { - Assert.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); - Assert.That( ( (MenuBarItem)m.Bar.Menus[ 0 ].Children[ 1 ] ).Children[ 0 ], Is.SameAs( m.TopChild ) ); - Assert.That( ( (MenuBarItem)m.Bar.Menus[ 0 ].Children[ 1 ] ).Children[ 1 ], Is.SameAs( bottomChild ) ); - } ); - } + Design? designBackIn = null; - [Test] - [TestOf( typeof( RemoveMenuItemOperation ) )] - public void DeletingMenuItemFromSubmenu_TopChild( ) - { - using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); + Assume.That( ( ) => designBackIn = codeToView!.CreateInstance( ), Throws.Nothing ); + Assume.That( designBackIn, Is.Not.Null.And.InstanceOf( ) ); - RemoveMenuItemOperation cmd = new ( m.TopChild ); - bool cmdSucceeded = false; - Assert.That( ( ) => cmdSucceeded = cmd.Do( ), Throws.Nothing ); - Assert.That( cmdSucceeded ); + MenuBar? mbIn = null; + Assume.That( designBackIn!.View, Is.Not.Null.And.InstanceOf( ) ); - // Delete the top child should leave only 1 in submenu - Assert.Multiple( ( ) => - { - Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assert.That( m.Head2.Children, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.That( m.Head2.Children[ 0 ], Is.Not.SameAs( m.TopChild ) ); - } ); + IList actualSubviews = designBackIn.View.GetActualSubviews( ); + Assert.That( actualSubviews, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( ( ) => mbIn = actualSubviews.OfType( ).Single( ), Throws.Nothing ); + Assert.That( mbIn, Is.Not.Null.And.InstanceOf( ) ); - Assert.That( cmd.Undo, Throws.Nothing ); + // 1 visible root menu (e.g. File) + Assert.That( mbIn!.Menus, Has.Exactly( 1 ).InstanceOf( ) ); + // 3 child menu item (original one + 3 we added -1 because we moved it to submenu) + Assert.That( mbIn.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); + Assert.That( mbIn.Menus[ 0 ].Children, Has.All.Not.Null ); - Assert.Multiple( ( ) => - { - // should come back now - Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assert.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); - Assert.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); - } ); + // should be 1 submenu item (the one we moved) + Assert.That( ( (MenuBarItem)mbIn.Menus[ 0 ].Children[ 0 ] ).Children, Has.Exactly( 1 ).InstanceOf( ) ); + Assert.That( + ( (MenuBarItem)mbIn.Menus[ 0 ].Children[ 0 ] ).Children[ 0 ].Title, + Is.EqualTo( ( (MenuBarItem)mbOut.Menus[ 0 ].Children[ 0 ] ).Children[ 0 ].Title ) ); } [Test] @@ -465,48 +635,6 @@ public void TestMenuOperations( ) MenuTracker.Instance.UnregisterMenuBar( mbOut ); } - [Test] - [TestOf( typeof( MoveMenuItemLeftOperation ) )] - public void MoveMenuItemLeft_CannotMoveRootItems( ) - { - using MenuBar bar = GetMenuBar( ); - - // cannot move a root item - MoveMenuItemLeftOperation moveMenuItemLeftOperation = new( bar.Menus[ 0 ].Children[ 0 ] ); - Assert.That( moveMenuItemLeftOperation.IsImpossible ); - bool moveMenuItemLeftOperationSucceeded = false; - Assert.That( ( ) => moveMenuItemLeftOperationSucceeded = moveMenuItemLeftOperation.Do( ), Throws.Nothing ); - Assert.That( moveMenuItemLeftOperationSucceeded, Is.False ); - } - - [Test] - public void MoveMenuItemLeft_MoveTopChild( ) - { - using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); - - MoveMenuItemLeftOperation moveMenuItemLeftOperation = new ( m.TopChild ); - Assert.That( moveMenuItemLeftOperation.IsImpossible, Is.False ); - bool moveMenuItemLeftOperationSucceeded = false; - Assert.That( ( ) => moveMenuItemLeftOperationSucceeded = moveMenuItemLeftOperation.Do( ), Throws.Nothing ); - Assert.That( moveMenuItemLeftOperationSucceeded ); - - // move the top child left should pull - // it out of the submenu and onto the root - Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 4 ).InstanceOf( ) ); - Assert.That( m.Head2.Children, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.That( m.Head2.Children[ 0 ], Is.Not.SameAs( m.TopChild ) ); - - // it should be pulled out underneath its parent - Assert.That( m.Bar.Menus[ 0 ].Children[ 2 ], Is.SameAs( m.TopChild ) ); - - // undoing command should return us to previous state - Assert.That( moveMenuItemLeftOperation.Undo, Throws.Nothing ); - - Assert.That( m.Bar.Menus[ 0 ].Children, Has.Exactly( 3 ).InstanceOf( ) ); - Assert.That( m.Head2.Children, Has.Exactly( 2 ).InstanceOf( ) ); - Assert.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); - } - [Test] [TestOf( typeof( AddMenuItemOperation ) )] public void TestMoveMenuItemRight_CannotMoveElementZero( ) @@ -595,53 +723,6 @@ public void TestMoveMenuItemRight_CannotMoveElementZero( ) } ); } - /// - /// Tests that when there is only one menu item - /// that it cannot be moved into a submenu - /// - [Test] - [TestOf( typeof( MoveMenuItemRightOperation ) )] - public void MoveMenuItemRight_CannotMoveLast( ) - { - MenuBar bar = GetMenuBar( ); - - MenuItem? mi = bar.Menus[ 0 ].Children[ 0 ]; - MoveMenuItemRightOperation cmd = new( mi ); - Assert.That( cmd.IsImpossible ); - Assert.That( cmd.Do, Is.False ); - } - - /// - /// Tests removing the last menu item (i.e. 'Do Something') - /// under the only remaining menu header (e.g. 'File F9') - /// should result in a completely empty menu bar and be undoable - /// - [Test] - [TestOf( typeof( RemoveMenuItemOperation ) )] - public void RemoveFinalMenuItemOnBar( ) - { - using MenuBar bar = GetMenuBar( ); - - MenuBarItem? fileMenu = bar.Menus[ 0 ]; - MenuItem? placeholderMenuItem = fileMenu.Children[ 0 ]; - - RemoveMenuItemOperation removeOp = new( placeholderMenuItem ); - - // we are able to remove the last one - Assert.That( removeOp.IsImpossible, Is.False ); - bool removeOpSucceeded = false; - Assert.That( ( ) => removeOpSucceeded = removeOp.Do( ), Throws.Nothing ); - Assert.That( removeOpSucceeded ); - Assert.That( bar.Menus, Is.Empty ); - - Assert.That( removeOp.Undo, Throws.Nothing ); - - // should be back to where we started - Assert.That( bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.SameAs( placeholderMenuItem ) ); - } - private static MenuBar GetMenuBar( ) { return GetMenuBar( out _ ); @@ -658,87 +739,6 @@ private static MenuBar GetMenuBar( out Design root ) return bar; } - [Test] - [TestOf( typeof( MenuBarTests ) )] - [Category( "Change Control" )] - [Order( 1 )] - [Repeat( 10 )] - [Description( "Ensures that the GetMenuBar helper method returns the expected objects and doesn't fail even if used multiple times." )] - public void GetMenuBar_BehavesAsExpected( ) - { - using MenuBar bar = GetMenuBar( out Design root ); - Assert.Multiple( ( ) => - { - Assert.That( bar, Is.Not.Null.And.InstanceOf( ) ); - Assert.That( root, Is.Not.Null.And.InstanceOf( ) ); - } ); - Assert.That( root.View.Subviews, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.Multiple( ( ) => - { - Assert.That( root.View.Subviews[ 0 ], Is.Not.Null.And.SameAs( bar ) ); - Assert.That( bar.Menus, Is.Not.Null ); - } ); - Assert.That( bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.That( bar.Menus[ 0 ], Is.Not.Null.And.InstanceOf( ) ); - Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 1 ).InstanceOf( ) ); - Assert.Multiple( ( ) => - { - Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); - Assert.That( OperationManager.Instance.UndoStackSize, Is.Zero ); - Assert.That( OperationManager.Instance.RedoStackSize, Is.Zero ); - } ); - } - - [Test] - [TestOf( typeof( MenuBarWithSubmenuItems ) )] - [Category( "Change Control" )] - [Order( 2 )] - [Repeat( 10 )] - [Description( "Ensures that the GetMenuBarWithSubmenuItems helper method returns the expected objects and doesn't fail even if used multiple times." )] - public void GetMenuBarWithSubmenuItems_BehavesAsExpected( ) - { - using MenuBarWithSubmenuItems m = GetMenuBarWithSubmenuItems( ); - - Assert.That( m.Bar.Menus, Has.Exactly( 1 ).InstanceOf( ) ); - - MenuBarItem menu0 = m.Bar.Menus[ 0 ]; - Assert.That( menu0.Children, Has.Exactly( 3 ).InstanceOf( ) ); - - // First item - MenuItem menu0Child0 = menu0.Children[ 0 ]; - Assert.That( menu0Child0.Title, Is.EqualTo( "Head1" ) ); - - // Second item and its children - Assert.That( menu0.Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); - MenuBarItem menu0Child1 = (MenuBarItem)menu0.Children[ 1 ]; - Assert.Multiple( ( ) => - { - Assert.That( menu0Child1.Title, Is.EqualTo( "Head2" ) ); - Assert.That( menu0Child1.Children, Has.Exactly( 2 ).InstanceOf( ) ); - } ); - MenuItem menu0Child1Leaf0 = menu0Child1.Children[ 0 ]; - MenuItem menu0Child1Leaf1 = menu0Child1.Children[ 1 ]; - Assert.Multiple( ( ) => - { - Assert.That( menu0Child1Leaf0.Title, Is.EqualTo( "Child1" ) ); - Assert.That( menu0Child1Leaf0.Shortcut, Is.EqualTo( Key.J.WithCtrl.KeyCode ) ); - Assert.That( menu0Child1Leaf1.Title, Is.EqualTo( "Child2" ) ); - Assert.That( menu0Child1Leaf1.Shortcut, Is.EqualTo( Key.F.WithCtrl.KeyCode ) ); - } ); - - // Third item - Assert.That( menu0.Children[ 2 ], Is.Not.Null.And.InstanceOf( ) ); - MenuItem menu0Child2 = menu0.Children[ 2 ]; - Assert.That( menu0Child2.Title, Is.EqualTo( "Head3" ) ); - - //Now just make sure the record properties were set to the right references - Assert.Multiple( ( ) => - { - Assert.That( m.Head2, Is.SameAs( menu0Child1 ) ); - Assert.That( m.TopChild, Is.SameAs( menu0Child1Leaf0 ) ); - } ); - } - private static MenuBarWithSubmenuItems GetMenuBarWithSubmenuItems( ) { MenuBarWithSubmenuItems toReturn = new( GetMenuBar( ), null!, null! ) @@ -754,21 +754,21 @@ private static MenuBarWithSubmenuItems GetMenuBarWithSubmenuItems( ) Head3 Child2 */ - var mi = toReturn.Bar.Menus[0].Children[0]; + var mi = toReturn.Bar.Menus[ 0 ].Children[ 0 ]; mi.Title = "Head1"; toReturn.Bar.Menus[ 0 ].Children = [ toReturn.Bar.Menus[ 0 ].Children[ 0 ], toReturn.Head2 = CreateHead2Item( ), - new ( "Head3", null, static ( ) => { } ), + new( "Head3", null, static ( ) => { } ), ]; return toReturn; MenuBarItem CreateHead2Item( ) { - return new( [ toReturn.TopChild = CreateHead2Child1Item( ), CreateHead2Child2Item( )] ) + return new( [toReturn.TopChild = CreateHead2Child1Item( ), CreateHead2Child2Item( )] ) { Title = "Head2", }; @@ -795,14 +795,13 @@ static MenuItem CreateHead2Child2Item( ) internal record MenuBarWithSubmenuItems( MenuBar Bar, MenuBarItem Head2, MenuItem TopChild ) : IDisposable { + public MenuBarItem Head2 { get; set; } = Head2; + public MenuItem TopChild { get; set; } = TopChild; + /// public void Dispose( ) { Bar.Dispose( ); } - - public MenuBarItem Head2 { get; set; } = Head2; - public MenuItem TopChild { get; set; } = TopChild; } - } \ No newline at end of file From 616335ec154a25909d0306c08ab576ac3a938ddc Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 23:12:19 -0700 Subject: [PATCH 54/55] One more rename/sort and make the record private sealed --- tests/MenuBarTests.cs | 178 +++++++++++++++++++++--------------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/tests/MenuBarTests.cs b/tests/MenuBarTests.cs index 0ee60567..6eea6ee3 100644 --- a/tests/MenuBarTests.cs +++ b/tests/MenuBarTests.cs @@ -256,6 +256,94 @@ public void MoveMenuItemLeft_MoveTopChild( ) Assert.That( m.Head2.Children[ 0 ], Is.SameAs( m.TopChild ) ); } + [Test] + [TestOf( typeof( AddMenuItemOperation ) )] + public void MoveMenuItemRight_CannotMoveElementZero( ) + { + using MenuBar bar = GetMenuBar( ); + + MenuItem? mi = bar.Menus[ 0 ].Children[ 0 ]; + mi.Data = "yarg"; + mi.Shortcut = Key.Y.WithCtrl.KeyCode; + + AddMenuItemOperation addAnother = new( mi ); + Assert.That( addAnother.IsImpossible, Is.False ); + bool addAnotherSucceeded = false; + Assert.That( ( ) => addAnotherSucceeded = addAnother.Do( ), Throws.Nothing ); + Assert.That( addAnotherSucceeded ); + + // should have added below us + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.SameAs( mi ) ); + Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.SameAs( mi ) ); + } ); + + // cannot move element 0 + MoveMenuItemRightOperation impossibleMoveRightOp = new( bar.Menus[ 0 ].Children[ 0 ] ); + Assert.That( impossibleMoveRightOp.IsImpossible ); + bool impossibleMoveRightOpSucceeded = false; + Assert.That( ( ) => impossibleMoveRightOpSucceeded = impossibleMoveRightOp.Do( ), Throws.Nothing ); + Assert.That( impossibleMoveRightOpSucceeded, Is.False ); + + // can move element 1 + // This is a destructive action, so references will change. + MoveMenuItemRightOperation validMoveRightOp = new( bar.Menus[ 0 ].Children[ 1 ] ); + Assert.That( validMoveRightOp.IsImpossible, Is.False ); + bool validMoveRightOpSucceeded = false; + Assert.That( ( ) => validMoveRightOpSucceeded = validMoveRightOp.Do( ), Throws.Nothing ); + Assert.That( validMoveRightOpSucceeded ); + + // We will have changed from a MenuItem to a MenuBarItem + // so element 0 will not be us. In Terminal.Gui there is + // a different class for a menu item and one with submenus. + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + MenuBarItem miConvertedToMenuBarItem = (MenuBarItem)bar.Menus[ 0 ].Children[ 0 ]; + + // Check that the references are unequal but values are equal + Assert.Multiple( ( ) => + { + Assert.That( miConvertedToMenuBarItem, Is.Not.SameAs( mi ) ); + Assert.That( miConvertedToMenuBarItem.Title, Is.EqualTo( mi.Title ) ); + Assert.That( miConvertedToMenuBarItem.Data, Is.EqualTo( mi.Data ) ); + Assert.That( miConvertedToMenuBarItem.Children, Has.Exactly( 1 ).InstanceOf( ) ); + } ); + + // Now undo it. + // This is destructive as well. + Assert.That( validMoveRightOp.Undo, Throws.Nothing ); + + Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); + Assert.Multiple( ( ) => + { + Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); + Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); + } ); + MenuItem firstChildAfterUndo = bar.Menus[ 0 ].Children[ 0 ]; + MenuItem secondChildAfterUndo = bar.Menus[ 0 ].Children[ 1 ]; + + Assert.Multiple( ( ) => + { + // All the previous references are gone forever through this process. + // So, neither element should be mi. + Assert.That( firstChildAfterUndo, Is.Not.SameAs( mi ) ); + Assert.That( secondChildAfterUndo, Is.Not.SameAs( mi ) ); + + // Neither element should be miConvertedToMenuBarItem either + Assert.That( firstChildAfterUndo, Is.Not.SameAs( miConvertedToMenuBarItem ) ); + Assert.That( secondChildAfterUndo, Is.Not.SameAs( miConvertedToMenuBarItem ) ); + + // And mi still should not be miConvertedToMenuBarItem + Assert.That( mi, Is.Not.SameAs( miConvertedToMenuBarItem ) ); + + // But the values need to be preserved + Assert.That( firstChildAfterUndo.Title, Is.EqualTo( mi.Title ) ); + Assert.That( firstChildAfterUndo.Data, Is.EqualTo( mi.Data ) ); + Assert.That( firstChildAfterUndo.Shortcut, Is.EqualTo( mi.Shortcut ) ); + } ); + } + /// /// Tests that when there is only one menu item /// that it cannot be moved into a submenu @@ -635,94 +723,6 @@ public void TestMenuOperations( ) MenuTracker.Instance.UnregisterMenuBar( mbOut ); } - [Test] - [TestOf( typeof( AddMenuItemOperation ) )] - public void TestMoveMenuItemRight_CannotMoveElementZero( ) - { - using MenuBar bar = GetMenuBar( ); - - MenuItem? mi = bar.Menus[ 0 ].Children[ 0 ]; - mi.Data = "yarg"; - mi.Shortcut = Key.Y.WithCtrl.KeyCode; - - AddMenuItemOperation addAnother = new( mi ); - Assert.That( addAnother.IsImpossible, Is.False ); - bool addAnotherSucceeded = false; - Assert.That( ( ) => addAnotherSucceeded = addAnother.Do( ), Throws.Nothing ); - Assert.That( addAnotherSucceeded ); - - // should have added below us - Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); - Assert.Multiple( ( ) => - { - Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.SameAs( mi ) ); - Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.SameAs( mi ) ); - } ); - - // cannot move element 0 - MoveMenuItemRightOperation impossibleMoveRightOp = new( bar.Menus[ 0 ].Children[ 0 ] ); - Assert.That( impossibleMoveRightOp.IsImpossible ); - bool impossibleMoveRightOpSucceeded = false; - Assert.That( ( ) => impossibleMoveRightOpSucceeded = impossibleMoveRightOp.Do( ), Throws.Nothing ); - Assert.That( impossibleMoveRightOpSucceeded, Is.False ); - - // can move element 1 - // This is a destructive action, so references will change. - MoveMenuItemRightOperation validMoveRightOp = new( bar.Menus[ 0 ].Children[ 1 ] ); - Assert.That( validMoveRightOp.IsImpossible, Is.False ); - bool validMoveRightOpSucceeded = false; - Assert.That( ( ) => validMoveRightOpSucceeded = validMoveRightOp.Do( ), Throws.Nothing ); - Assert.That( validMoveRightOpSucceeded ); - - // We will have changed from a MenuItem to a MenuBarItem - // so element 0 will not be us. In Terminal.Gui there is - // a different class for a menu item and one with submenus. - Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); - MenuBarItem miConvertedToMenuBarItem = (MenuBarItem)bar.Menus[ 0 ].Children[ 0 ]; - - // Check that the references are unequal but values are equal - Assert.Multiple( ( ) => - { - Assert.That( miConvertedToMenuBarItem, Is.Not.SameAs( mi ) ); - Assert.That( miConvertedToMenuBarItem.Title, Is.EqualTo( mi.Title ) ); - Assert.That( miConvertedToMenuBarItem.Data, Is.EqualTo( mi.Data ) ); - Assert.That( miConvertedToMenuBarItem.Children, Has.Exactly( 1 ).InstanceOf( ) ); - } ); - - // Now undo it. - // This is destructive as well. - Assert.That( validMoveRightOp.Undo, Throws.Nothing ); - - Assert.That( bar.Menus[ 0 ].Children, Has.Exactly( 2 ).InstanceOf( ) ); - Assert.Multiple( ( ) => - { - Assert.That( bar.Menus[ 0 ].Children[ 0 ], Is.Not.Null.And.InstanceOf( ) ); - Assert.That( bar.Menus[ 0 ].Children[ 1 ], Is.Not.Null.And.InstanceOf( ) ); - } ); - MenuItem firstChildAfterUndo = bar.Menus[ 0 ].Children[ 0 ]; - MenuItem secondChildAfterUndo = bar.Menus[ 0 ].Children[ 1 ]; - - Assert.Multiple( ( ) => - { - // All the previous references are gone forever through this process. - // So, neither element should be mi. - Assert.That( firstChildAfterUndo, Is.Not.SameAs( mi ) ); - Assert.That( secondChildAfterUndo, Is.Not.SameAs( mi ) ); - - // Neither element should be miConvertedToMenuBarItem either - Assert.That( firstChildAfterUndo, Is.Not.SameAs( miConvertedToMenuBarItem ) ); - Assert.That( secondChildAfterUndo, Is.Not.SameAs( miConvertedToMenuBarItem ) ); - - // And mi still should not be miConvertedToMenuBarItem - Assert.That( mi, Is.Not.SameAs( miConvertedToMenuBarItem ) ); - - // But the values need to be preserved - Assert.That( firstChildAfterUndo.Title, Is.EqualTo( mi.Title ) ); - Assert.That( firstChildAfterUndo.Data, Is.EqualTo( mi.Data ) ); - Assert.That( firstChildAfterUndo.Shortcut, Is.EqualTo( mi.Shortcut ) ); - } ); - } - private static MenuBar GetMenuBar( ) { return GetMenuBar( out _ ); @@ -793,7 +793,7 @@ static MenuItem CreateHead2Child2Item( ) } } - internal record MenuBarWithSubmenuItems( MenuBar Bar, MenuBarItem Head2, MenuItem TopChild ) : IDisposable + private sealed record MenuBarWithSubmenuItems( MenuBar Bar, MenuBarItem Head2, MenuItem TopChild ) : IDisposable { public MenuBarItem Head2 { get; set; } = Head2; public MenuItem TopChild { get; set; } = TopChild; From 9b19f17b6e833fb6cb55b5096e29f321bf6ed333 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Sat, 30 Dec 2023 23:20:52 -0700 Subject: [PATCH 55/55] Collection expressions Enables compiler optimizations not possible with the old style. --- src/ViewFactory.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ViewFactory.cs b/src/ViewFactory.cs index acbfe847..2a2128d2 100644 --- a/src/ViewFactory.cs +++ b/src/ViewFactory.cs @@ -28,7 +28,7 @@ public static class ViewFactory internal const string DefaultMenuItemText = "Edit Me"; internal static readonly Type[] KnownUnsupportedTypes = - { + [ typeof( Toplevel ), typeof( Dialog ), typeof( FileDialog ), @@ -43,7 +43,7 @@ public static class ViewFactory // BUG These seem to cause stack overflows in CreateSubControlDesigns (see TestAddView_RoundTrip) typeof( Wizard ), typeof( WizardStep ) - }; + ]; /// /// Gets a new instance of a default [], to include as the default initial @@ -58,12 +58,11 @@ internal static MenuBarItem[] DefaultMenuBarItems { get { - return new[] - { - new MenuBarItem( - "_File (F9)", - new[] { new MenuItem( DefaultMenuItemText, string.Empty, ( ) => { } ) } ) - }; + return + [ + new( "_File (F9)", + [ new MenuItem( DefaultMenuItemText, string.Empty, static ( ) => { } ) ] ) + ]; } }