From 9e3fe968f514e9048a406c2700e7ccd0333a74fe Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Tue, 19 Dec 2023 08:08:39 -0700 Subject: [PATCH 01/76] Add a test method for IsCenter (with same ignore condition as the others) --- tests/PosTests.cs | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/tests/PosTests.cs b/tests/PosTests.cs index 130d5e8d..bb22ac80 100644 --- a/tests/PosTests.cs +++ b/tests/PosTests.cs @@ -196,6 +196,31 @@ private static IEnumerable IsAnchorEnd_Cases }; } } + + private static IEnumerable IsCenter_Cases + { + get + { + View v = new( ); + _ = new Design( new( new FileInfo( "yarg.cs" ) ), "myView", v ); + + return new TestCaseData[] + { + new ExpectedFalseTestCaseData( Pos.At( 50 ) ), + new ExpectedFalseTestCaseData( null ), + new ExpectedFalseTestCaseData( Pos.AnchorEnd( ) ), + new ExpectedFalseTestCaseData( Pos.AnchorEnd( 5 ) ), + new ExpectedTrueTestCaseData( Pos.Center( ) ), + new ExpectedFalseTestCaseData( Pos.Percent( 5 ) ), + new ExpectedFalseTestCaseData( Pos.Top( v ) ), + new ExpectedFalseTestCaseData( Pos.Bottom( v ) ), + new ExpectedFalseTestCaseData( Pos.Left( v ) ), + new ExpectedFalseTestCaseData( Pos.Right( v ) ), + new ExpectedFalseTestCaseData( Pos.Y( v ) ), + new ExpectedFalseTestCaseData( Pos.X( v ) ) + }; + } + } private static IEnumerable IsAnchorEnd_WithOutParam_Cases { @@ -269,6 +294,7 @@ private static IEnumerable IsRelative_Cases return new TestCaseData[] { new ExpectedFalseTestCaseData( Pos.At( 50 ) ), + new ExpectedFalseTestCaseData( null ), new ExpectedFalseTestCaseData( Pos.AnchorEnd( 5 ) ), new ExpectedFalseTestCaseData( Pos.Center( ) ), new ExpectedFalseTestCaseData( Pos.Percent( 5 ) ), @@ -398,6 +424,19 @@ public bool IsAnchorEnd_WithOutParam( Pos testValue, int expectedOutValue ) return isAnchorEnd; } + [Test] + [TestCaseSource( nameof( IsCenter_Cases ) )] + [NonParallelizable] + public bool IsCenter( Pos? testValue ) + { + if ( testValue is null ) + { + Assert.Warn( "BUG: Null returns true for this, when it shouldn't" ); + Assert.Ignore( "BUG: Null returns true for this, when it shouldn't" ); + } + return testValue.IsCenter( ); + } + [Test] [TestCaseSource( nameof( IsPercent_Cases ) )] [NonParallelizable] @@ -419,8 +458,13 @@ public bool IsPercent_WithOutParam( Pos testValue, float expectedOutValue ) [Test] [TestCaseSource( nameof( IsRelative_Cases ) )] [NonParallelizable] - public bool IsRelative( Pos testValue ) + public bool IsRelative( Pos? testValue ) { + if ( testValue is null ) + { + Assert.Ignore( "BUG: Null returns true for this, when it shouldn't" ); + } + return testValue.IsRelative( ); } From ac08cac8600f78ee0c9a7f2c5cf6b5c14dbe4b4b Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Tue, 19 Dec 2023 08:10:15 -0700 Subject: [PATCH 02/76] Add a test session file for convenience. There's a bug in the resharper test runner right now that makes it hang when running coverage, if there's more than one runtime targeted. This session limits it to one runtime, if imported. --- tests/All tests from net8.0.testsession | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/All tests from net8.0.testsession diff --git a/tests/All tests from net8.0.testsession b/tests/All tests from net8.0.testsession new file mode 100644 index 00000000..2329087b --- /dev/null +++ b/tests/All tests from net8.0.testsession @@ -0,0 +1,3 @@ + + net8.0 + \ No newline at end of file From 17dc708d7bb1d0a21d9a40022f96c7904e3cac50 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Tue, 19 Dec 2023 09:01:50 -0700 Subject: [PATCH 03/76] Move these to a different class and refactor a bit --- src/MenuBarExtensions.cs | 27 +--------------- src/ReflectionHelpers.cs | 66 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 26 deletions(-) create mode 100644 src/ReflectionHelpers.cs diff --git a/src/MenuBarExtensions.cs b/src/MenuBarExtensions.cs index 8620e5ef..645db5e7 100644 --- a/src/MenuBarExtensions.cs +++ b/src/MenuBarExtensions.cs @@ -17,7 +17,7 @@ public static class MenuBarExtensions /// Selected or null if none. public static MenuBarItem? GetSelectedMenuItem(this MenuBar menuBar) { - var selected = (int)GetNonNullPrivateFieldValue("selected", menuBar, typeof(MenuBar)); + int selected = menuBar.GetNonNullPrivateFieldValue( "selected" ); if (selected < 0 || selected >= menuBar.Menus.Length) { @@ -75,29 +75,4 @@ public static class MenuBarExtensions // Return the last menu item that begins rendering before this X point return menuXLocations.Last(m => m.Key <= clientPoint.X).Value; } - - /// - /// Changes the even though it has no setter in Terminal.Gui. - /// - /// to change on. - /// The new value for . - public static void SetShortcut(this StatusItem item, Key newShortcut) - { - // See: https://stackoverflow.com/a/40917899/4824531 - const string backingFieldName = "k__BackingField"; - - var field = - typeof(StatusItem).GetField(backingFieldName, BindingFlags.Instance | BindingFlags.NonPublic) - ?? throw new Exception($"Could not find auto backing field '{backingFieldName}'"); - - field.SetValue(item, newShortcut); - } - - private static object GetNonNullPrivateFieldValue(string fieldName, object item, Type type) - { - var selectedField = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance) - ?? throw new Exception($"Expected private field {fieldName} was not present on {type.Name}"); - return selectedField.GetValue(item) - ?? throw new Exception($"Private field {fieldName} was unexpectedly null on {type.Name}"); - } } diff --git a/src/ReflectionHelpers.cs b/src/ReflectionHelpers.cs new file mode 100644 index 00000000..e2b67f63 --- /dev/null +++ b/src/ReflectionHelpers.cs @@ -0,0 +1,66 @@ +using System.Reflection; +using Terminal.Gui; + +namespace TerminalGuiDesigner; + +/// Helper methods to simplify reflection operations. +public static class ReflectionHelpers +{ + /// + /// Gets a private field value from a non-null inheriting from as a + /// . + /// + /// The type of the field. Must pass a constraint. + /// + /// The type of the to get the field on. Can be inferred from usage. + /// + /// The to reflect on. + /// The name of the field to get via reflection. + /// + /// A non-null from the reflected private field of . + /// + /// + /// If the requested does not exist on type . + /// + /// + /// If the requested on type is not of type . + /// + /// + /// If the value of the requested on was null. + /// + internal static TOut GetNonNullPrivateFieldValue( this TIn? item, string fieldName ) + where TIn : View + where TOut : notnull + { + ArgumentNullException.ThrowIfNull( item, nameof( item ) ); + ArgumentException.ThrowIfNullOrEmpty( fieldName, nameof( fieldName ) ); + + FieldInfo selectedField = typeof( TIn ).GetField( fieldName, BindingFlags.NonPublic | BindingFlags.Instance ) + ?? throw new MissingFieldException( $"Expected private instance field {fieldName} was not present on {typeof( TIn ).Name}" ); + + if ( selectedField.FieldType != typeof( TOut ) ) + { + throw new FieldAccessException( $"Field {fieldName} on {typeof( TIn ).Name} is not of expected type {typeof( TOut ).Name}" ); + } + + return (TOut)( selectedField.GetValue( item ) + ?? throw new InvalidOperationException( $"Private instance field {fieldName} was unexpectedly null on {typeof( TIn ).Name}" ) ); + } + + /// + /// Changes the even though it has no setter in Terminal.Gui. + /// + /// to change on. + /// The new value for . + public static void SetShortcut( this StatusItem item, Key newShortcut ) + { + // See: https://stackoverflow.com/a/40917899/4824531 + const string backingFieldName = "k__BackingField"; + + var field = + typeof( StatusItem ).GetField( backingFieldName, BindingFlags.Instance | BindingFlags.NonPublic ) + ?? throw new Exception( $"Could not find auto backing field '{backingFieldName}'" ); + + field.SetValue( item, newShortcut ); + } +} From 7bcc12e7da92d93bf0aefdb2c4db31a332af6726 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Tue, 19 Dec 2023 09:03:53 -0700 Subject: [PATCH 04/76] Terminal.Gui now has a setter for this, so we can get rid of this --- src/MenuBarExtensions.cs | 4 ++-- .../StatusBarOperations/SetShortcutOperation.cs | 6 +++--- src/ReflectionHelpers.cs | 17 ----------------- tests/MenuBarExtensionsTests.cs | 10 ---------- 4 files changed, 5 insertions(+), 32 deletions(-) diff --git a/src/MenuBarExtensions.cs b/src/MenuBarExtensions.cs index 645db5e7..a13549f9 100644 --- a/src/MenuBarExtensions.cs +++ b/src/MenuBarExtensions.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using Terminal.Gui; +using Terminal.Gui; namespace TerminalGuiDesigner; @@ -36,6 +35,7 @@ public static class MenuBarExtensions public static MenuBarItem? ScreenToMenuBarItem(this MenuBar menuBar, int screenX) { // These might be changed in Terminal.Gui library + // TODO: Maybe load these from a config file, so we aren't at TG's mercy const int initialWhitespace = 1; const int afterEachItemWhitespace = 2; diff --git a/src/Operations/StatusBarOperations/SetShortcutOperation.cs b/src/Operations/StatusBarOperations/SetShortcutOperation.cs index 4c3abb92..a28a1f1b 100644 --- a/src/Operations/StatusBarOperations/SetShortcutOperation.cs +++ b/src/Operations/StatusBarOperations/SetShortcutOperation.cs @@ -39,13 +39,13 @@ public override void Redo() return; } - this.OperateOn.SetShortcut(this.shortcut.Value); + this.OperateOn.Shortcut = this.shortcut.Value; } /// public override void Undo() { - this.OperateOn.SetShortcut(this.originalShortcut); + this.OperateOn.Shortcut = this.originalShortcut; } /// @@ -56,7 +56,7 @@ protected override bool DoImpl() this.shortcut = Modals.GetShortcut(); } - this.OperateOn.SetShortcut(this.shortcut.Value); + this.OperateOn.Shortcut = this.shortcut.Value; return true; } } diff --git a/src/ReflectionHelpers.cs b/src/ReflectionHelpers.cs index e2b67f63..80730e50 100644 --- a/src/ReflectionHelpers.cs +++ b/src/ReflectionHelpers.cs @@ -46,21 +46,4 @@ internal static TOut GetNonNullPrivateFieldValue( this TIn? item, str return (TOut)( selectedField.GetValue( item ) ?? throw new InvalidOperationException( $"Private instance field {fieldName} was unexpectedly null on {typeof( TIn ).Name}" ) ); } - - /// - /// Changes the even though it has no setter in Terminal.Gui. - /// - /// to change on. - /// The new value for . - public static void SetShortcut( this StatusItem item, Key newShortcut ) - { - // See: https://stackoverflow.com/a/40917899/4824531 - const string backingFieldName = "k__BackingField"; - - var field = - typeof( StatusItem ).GetField( backingFieldName, BindingFlags.Instance | BindingFlags.NonPublic ) - ?? throw new Exception( $"Could not find auto backing field '{backingFieldName}'" ); - - field.SetValue( item, newShortcut ); - } } diff --git a/tests/MenuBarExtensionsTests.cs b/tests/MenuBarExtensionsTests.cs index 04b8e195..84553dc4 100644 --- a/tests/MenuBarExtensionsTests.cs +++ b/tests/MenuBarExtensionsTests.cs @@ -146,14 +146,4 @@ public void ScreenToMenuBarItem_OneMenuItem_ReturnsNull_IfClickedBeforeAndAfterI "Expected Terminal.Gui MenuBar to have 1 unit of whitespace before and 2 after any MenuBarItems (e.g. File) get rendered. This may change in future, if so then update this test." ); }, out _ ); } - - [Test] - public void SetShortcut_SetsExpectedKeyCode( ) - { - var si = new StatusItem( Key.F, "ff", ( ) => { } ); - Assert.That( si.Shortcut, Is.EqualTo( Key.F ) ); - - si.SetShortcut( Key.B ); - Assert.That( si.Shortcut, Is.EqualTo( Key.B ) ); - } } From b7795dee2dc4b7be82b3e003377ad09e856ad1b2 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Tue, 19 Dec 2023 09:09:21 -0700 Subject: [PATCH 05/76] Re-wording to be more explicit about what it does (can return internal, private, protected, etc) --- src/MenuBarExtensions.cs | 2 +- src/ReflectionHelpers.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/MenuBarExtensions.cs b/src/MenuBarExtensions.cs index a13549f9..dcaa6f30 100644 --- a/src/MenuBarExtensions.cs +++ b/src/MenuBarExtensions.cs @@ -16,7 +16,7 @@ public static class MenuBarExtensions /// Selected or null if none. public static MenuBarItem? GetSelectedMenuItem(this MenuBar menuBar) { - int selected = menuBar.GetNonNullPrivateFieldValue( "selected" ); + int selected = menuBar.GetNonNullNonPublicFieldValue( "selected" ); if (selected < 0 || selected >= menuBar.Menus.Length) { diff --git a/src/ReflectionHelpers.cs b/src/ReflectionHelpers.cs index 80730e50..ce391f94 100644 --- a/src/ReflectionHelpers.cs +++ b/src/ReflectionHelpers.cs @@ -7,7 +7,7 @@ namespace TerminalGuiDesigner; public static class ReflectionHelpers { /// - /// Gets a private field value from a non-null inheriting from as a + /// Gets a non-public field value from a non-null inheriting from as a /// . /// /// The type of the field. Must pass a constraint. @@ -28,7 +28,7 @@ public static class ReflectionHelpers /// /// If the value of the requested on was null. /// - internal static TOut GetNonNullPrivateFieldValue( this TIn? item, string fieldName ) + internal static TOut GetNonNullNonPublicFieldValue( this TIn? item, string fieldName ) where TIn : View where TOut : notnull { @@ -36,7 +36,7 @@ internal static TOut GetNonNullPrivateFieldValue( this TIn? item, str ArgumentException.ThrowIfNullOrEmpty( fieldName, nameof( fieldName ) ); FieldInfo selectedField = typeof( TIn ).GetField( fieldName, BindingFlags.NonPublic | BindingFlags.Instance ) - ?? throw new MissingFieldException( $"Expected private instance field {fieldName} was not present on {typeof( TIn ).Name}" ); + ?? throw new MissingFieldException( $"Expected non-public instance field {fieldName} was not present on {typeof( TIn ).Name}" ); if ( selectedField.FieldType != typeof( TOut ) ) { @@ -44,6 +44,6 @@ internal static TOut GetNonNullPrivateFieldValue( this TIn? item, str } return (TOut)( selectedField.GetValue( item ) - ?? throw new InvalidOperationException( $"Private instance field {fieldName} was unexpectedly null on {typeof( TIn ).Name}" ) ); + ?? throw new InvalidOperationException( $"Non-public instance field {fieldName} was unexpectedly null on {typeof( TIn ).Name}" ) ); } } From eb1794874822bc8cf208cebb31164945f73fb88e Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Tue, 19 Dec 2023 09:11:46 -0700 Subject: [PATCH 06/76] Turns out that's not allowed in .net7 :( --- src/MenuBarExtensions.cs | 2 +- src/ReflectionHelpers.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MenuBarExtensions.cs b/src/MenuBarExtensions.cs index dcaa6f30..dee81396 100644 --- a/src/MenuBarExtensions.cs +++ b/src/MenuBarExtensions.cs @@ -16,7 +16,7 @@ public static class MenuBarExtensions /// Selected or null if none. public static MenuBarItem? GetSelectedMenuItem(this MenuBar menuBar) { - int selected = menuBar.GetNonNullNonPublicFieldValue( "selected" ); + int selected = menuBar.GetNonNullNonPublicFieldValue( "selected" ); if (selected < 0 || selected >= menuBar.Menus.Length) { diff --git a/src/ReflectionHelpers.cs b/src/ReflectionHelpers.cs index ce391f94..f437cad8 100644 --- a/src/ReflectionHelpers.cs +++ b/src/ReflectionHelpers.cs @@ -12,7 +12,7 @@ public static class ReflectionHelpers /// /// The type of the field. Must pass a constraint. /// - /// The type of the to get the field on. Can be inferred from usage. + /// The type of the to get the field on. /// /// The to reflect on. /// The name of the field to get via reflection. From 1a3e0e1c060ad488591c26ed9e27358e1e581639 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 16:07:02 -0700 Subject: [PATCH 07/76] Improve handling of the generic Create Also move last bit of reflection to the ReflectionHelpers class --- src/ReflectionHelpers.cs | 16 ++ src/ViewFactory.cs | 434 ++++++++++++++++----------------------- 2 files changed, 193 insertions(+), 257 deletions(-) diff --git a/src/ReflectionHelpers.cs b/src/ReflectionHelpers.cs index f437cad8..ebe4cb7a 100644 --- a/src/ReflectionHelpers.cs +++ b/src/ReflectionHelpers.cs @@ -46,4 +46,20 @@ internal static TOut GetNonNullNonPublicFieldValue( this TIn? item, s return (TOut)( selectedField.GetValue( item ) ?? throw new InvalidOperationException( $"Non-public instance field {fieldName} was unexpectedly null on {typeof( TIn ).Name}" ) ); } + + public static View GetDefaultViewInstance( Type t ) + { + if ( !t.IsAssignableTo( typeof(View) ) ) + { + throw new ArgumentOutOfRangeException( nameof( t ), $"{t.Name} must be assignable to the View type" ); + } + + var instance = Activator.CreateInstance( t ) as View ?? throw new Exception( $"CreateInstance returned null for Type '{t.Name}'" ); + instance.SetActualText( "Heya" ); + + instance.Width = Math.Max( instance.Bounds.Width, 4 ); + instance.Height = Math.Max( instance.Bounds.Height, 1 ); + + return instance; + } } diff --git a/src/ViewFactory.cs b/src/ViewFactory.cs index 05edbf3e..0e3df465 100644 --- a/src/ViewFactory.cs +++ b/src/ViewFactory.cs @@ -7,13 +7,27 @@ namespace TerminalGuiDesigner; /// -/// Creates new instances configured to have -/// sensible dimensions and content for dragging/configuring in -/// the designer. +/// Creates new instances configured to have sensible dimensions +/// and content for dragging/configuring in the designer. /// public static class ViewFactory { - internal static Type[] KnownUnsupportedTypes => new[] + /// + /// A constant defining the default text for a new menu item added via the + /// + /// + /// adds a new top level menu (e.g. File, Edit etc.).
+ /// In the designer, all menus must have at least 1 under them, so it will be + /// created with a single in it, already.
+ /// That item will bear this text.

+ /// This string should be used by any other areas of code that want to create new under + /// a top/sub menu (e.g. ). + ///
+ /// The string "Edit Me" + /// + internal const string DefaultMenuItemText = "Edit Me"; + + internal static readonly Type[] KnownUnsupportedTypes = { typeof( Toplevel ), typeof( Dialog ), @@ -22,32 +36,47 @@ public static class ViewFactory typeof( OpenDialog ), typeof( ScrollBarView ), typeof( TreeView<> ), - typeof( Slider<> ), // Theses are special types of view and shouldn't be added manually by user typeof( Frame ), - // BUG These seem to cause stack overflows in CreateSubControlDesigns (see TestAddView_RoundTrip) typeof( Wizard ), - typeof( WizardStep ), + typeof( WizardStep ) }; + /// + /// A static reference to () + /// internal static readonly Type ViewType = typeof(View); - internal static MenuBarItem[] DefaultMenuBarItems => - new[] + /// + /// Gets a new instance of a default [], to include as the default initial + /// + /// collection of a new + /// + /// + /// A new single-element array of , with default text, an empty + /// , and empty string. + /// + internal static MenuBarItem[] DefaultMenuBarItems + { + get { - new MenuBarItem( - "_File (F9)", - new[] { new MenuItem( DefaultMenuItemText, string.Empty, ( ) => { } ) } ) - }; + return new[] + { + new MenuBarItem( + "_File (F9)", + new[] { new MenuItem( DefaultMenuItemText, string.Empty, ( ) => { } ) } ) + }; + } + } /// - /// Gets all Types that are supported by . + /// Gets all Types that are supported by . /// - /// All types supported by . - internal static IEnumerable SupportedViewTypes { get; } = + /// An of s supported by . + public static IEnumerable SupportedViewTypes { get; } = ViewType.Assembly.DefinedTypes .Where( unfilteredType => unfilteredType is { @@ -57,28 +86,42 @@ public static class ViewFactory IsValueType: false } ) .Where( filteredType => filteredType.IsSubclassOf( ViewType ) ) - .Where( viewDescendantType => !KnownUnsupportedTypes.Any( viewDescendantType.IsAssignableTo ) ); + .Where( viewDescendantType => !KnownUnsupportedTypes.Any( viewDescendantType.IsAssignableTo ) + || viewDescendantType == typeof( Window ) ); + + private static bool IsSupportedType( this Type t ) + { + return t == typeof( Window ) || ( !KnownUnsupportedTypes.Any( t.IsSubclassOf ) & !KnownUnsupportedTypes.Contains( t ) ); + } /// - /// Creates a new instance of a of Type with - /// size/placeholder values that make it easy to see and design in the editor. + /// Creates a new instance of a of Type with + /// size/placeholder values that make it easy to see and design in the editor. /// - /// A descendant of that does not exist in the - /// collection. - /// The width of the requested view. - /// The height of the requested view. - /// Optional string to set the Text property of types that have one. - /// If an unsupported type is requested. - /// A new instance of . + /// + /// A concrete descendant type of that does not exist in the + /// collection and which has a public constructor. + /// + /// + /// An optional width of the requested view. Default values are dependent on the requested + /// type, if not supplied. + /// + /// + /// An optional height of the requested view. Default values are dependent on the requested + /// type, if not supplied. + /// + /// If an unsupported type is requested + /// + /// A new instance of with the specified dimensions or defaults, if not provided. + /// /// - /// must inherit from , - /// must have a public constructor, and must not exist in the - /// collection, at run-time. + /// must inherit from , must have a public constructor, and must + /// not exist in the collection, at run-time. /// public static T Create(int? width = null, int? height = null, string? text = null ) where T : View, new( ) { - if ( !SupportedViewTypes.Contains( typeof( T ) ) ) + if ( !IsSupportedType( typeof( T ) ) ) { throw new NotSupportedException( $"Requested type {typeof( T ).Name} is not supported" ); } @@ -93,263 +136,140 @@ public static T Create(int? width = null, int? height = null, string? text = dt.Columns.Add( "Column 1" ); dt.Columns.Add( "Column 2" ); dt.Columns.Add( "Column 3" ); - tv.Width = width ?? 50; - tv.Height = height ?? 5; + SetDefaultDimensions( newView, width ?? 50, height ?? 5 ); tv.Table = new DataTableSource( dt ); break; case TabView tv: - tv.AddEmptyTab("Tab1"); - tv.AddEmptyTab("Tab2"); - tv.Width = width ?? 50; - tv.Height = height ?? 5; + tv.AddEmptyTab( "Tab1" ); + tv.AddEmptyTab( "Tab2" ); + SetDefaultDimensions( newView, width ?? 50, height ?? 5 ); break; case TextValidateField tvf: tvf.Provider = new TextRegexProvider( ".*" ); tvf.Text = text ?? "Heya"; - newView.Width = width ?? 5; - newView.Height = height ?? 1; - goto default; + SetDefaultDimensions( newView, width ?? 5, height ?? 1 ); + break; + case DateField df: + df.Date = DateTime.Now; + SetDefaultDimensions( newView, width ?? 20, height ?? 1 ); + break; case TextField tf: tf.Text = text ?? "Heya"; - newView.Width = width ?? 5; - newView.Height = height ?? 1; - goto default; + SetDefaultDimensions( newView, width ?? 5, height ?? 1 ); + break; case ProgressBar pb: pb.Fraction = 1f; - pb.Width = width ?? 10; - pb.Height = height ?? 1; + SetDefaultDimensions( newView, width ?? 10, height ?? 1 ); break; case MenuBar mb: mb.Menus = DefaultMenuBarItems; break; - case Window w: - w.Width = width ?? 10; - w.Height = height ?? 5; + case StatusBar sb: + sb.Items = new[] { new StatusItem( Key.F1, "F1 - Edit Me", null ) }; + break; + case RadioGroup rg: + rg.RadioLabels = new string[] { "Option 1", "Option 2" }; + SetDefaultDimensions( newView, width ?? 10, height ?? 2 ); + break; + case GraphView gv: + gv.GraphColor = new Attribute( Color.White, Color.Black ); + SetDefaultDimensions( newView, width ?? 20, height ?? 5 ); + break; + case ListView lv: + lv.SetSource( new List { "Item1", "Item2", "Item3" } ); + SetDefaultDimensions( newView, width ?? 20, height ?? 3 ); + break; + case FrameView: + case HexView: + newView.SetActualText( text ?? "Heya" ); + SetDefaultDimensions( newView, width ?? 10, height ?? 5 ); + break; + case Window: + SetDefaultDimensions( newView, width ?? 10, height ?? 5 ); + break; + case LineView: + SetDefaultDimensions( newView, width ?? 8, height ?? 1 ); + break; + case TreeView: + SetDefaultDimensions( newView, width ?? 16, height ?? 5 ); + break; + case ScrollView sv: + sv.ContentSize = new Size( 20, 10 ); + SetDefaultDimensions(newView, width ?? 10, height ?? 5 ); break; case Label l: l.SetActualText( text ?? "Heya" ); - newView.Width = width ?? 5; - newView.Height = height ?? 1; - break; - default: - newView.Width = width ?? 5; - newView.Height = height ?? 1; + SetDefaultDimensions( newView, width ?? 4, height ?? 1 ); break; - } - - return newView; - } - - /// - /// Creates a new instance of of Type with - /// size/placeholder values that make it easy to see and design in the editor. - /// - /// A Type of . See for the - /// full list of allowed Types. - /// A new instance of Type . - /// Thrown if Type is not a subclass of . - public static View Create(Type t) - { - if (typeof(TableView).IsAssignableFrom(t)) - { - return CreateTableView( ); - } - - if (typeof(TabView).IsAssignableFrom(t)) - { - return CreateTabView( ); - } - - if (typeof(RadioGroup).IsAssignableFrom(t)) - { - return CreateRadioGroup( ); - } - - if (typeof(MenuBar).IsAssignableFrom(t)) - { - return CreateMenuBar( ); - } - - if (typeof(StatusBar).IsAssignableFrom(t)) - { - return new StatusBar(new[] { new StatusItem(Key.F1, "F1 - Edit Me", null) }); - } - - if (t == typeof(TextValidateField)) - { - return new TextValidateField - { - Provider = new TextRegexProvider(".*"), - Text = "Heya", - Width = 5, - Height = 1, - }; - } - - if (t == typeof(ProgressBar)) - { - return new ProgressBar - { - Width = 10, - Height = 1, - Fraction = 1f, - }; - } - - if (t == typeof(View)) - { - return new View - { - Width = 10, - Height = 5, - }; - } - - if (t == typeof(Window)) - { - return new Window - { - Width = 10, - Height = 5, - }; - } - - if (t == typeof(TextField)) - { - return new TextField - { - Width = 10, - Height = 1, - }; - } - - if (typeof(GraphView).IsAssignableFrom(t)) - { - return new GraphView - { - Width = 20, - Height = 5, - GraphColor = new Attribute(Color.White, Color.Black), - }; - } - - if (typeof(ListView).IsAssignableFrom(t)) - { - var lv = new ListView(new List { "Item1", "Item2", "Item3" }) - { - Width = 20, - Height = 3, - }; - - return lv; - } - - if (t == typeof(LineView)) - { - return new LineView() - { - Width = 8, - Height = 1, - }; - } - - if (t == typeof(TreeView)) - { - return new TreeView() - { - Width = 16, - Height = 5, - }; - } + case SpinnerView sv: + sv.AutoSpin = true; + if ( width is not null ) + { + sv.Width = width; + } - if (t == typeof(ScrollView)) - { - return new ScrollView() - { - Width = 10, - Height = 5, - ContentSize = new Size(20, 10), - }; - } + if ( height is not null ) + { + sv.Height = height; + } - if (typeof(SpinnerView).IsAssignableFrom(t)) - { - return new SpinnerView() { AutoSpin = true }; + break; + case not null when newView.GetType( ).IsSubclassOf( typeof(View) ): + // Case for view inheritors + SetDefaultDimensions( newView, width ?? 5, height ?? 1 ); + break; + case { }: + SetDefaultDimensions(newView, 10, 5); + break; + case null: + throw new InvalidOperationException( $"Unexpected null result from type {typeof( T ).Name} construtor." ); } - var instance = Activator.CreateInstance(t) as View ?? throw new Exception($"CreateInstance returned null for Type '{t}'"); - instance.SetActualText("Heya"); - - instance.Width = Math.Max(instance.Bounds.Width, 4); - instance.Height = Math.Max(instance.Bounds.Height, 1); + return newView; - if (instance is FrameView || instance is HexView) + static void SetDefaultDimensions( T v, int width = 5, int height = 1 ) { - instance.Height = 5; - instance.Width = 10; + v.Width = Math.Max( v.Bounds.Width, width ); + v.Height = Math.Max( v.Bounds.Height, height ); } - - return instance; - } - - private static MenuBar CreateMenuBar() - { - return new( DefaultMenuBarItems ); } - private static View CreateRadioGroup() - { - var group = new RadioGroup - { - Width = 10, - Height = 2, - }; - group.RadioLabels = new string[] { "Option 1", "Option 2" }; - - return group; - } - - private static TableView CreateTableView() - { - var dt = new DataTable(); - dt.Columns.Add("Column 0"); - dt.Columns.Add("Column 1"); - dt.Columns.Add("Column 2"); - dt.Columns.Add("Column 3"); - - return new TableView - { - Width = 50, - Height = 5, - Table = new DataTableSource(dt), - }; - } - - private static TabView CreateTabView() + /// + /// Creates a new instance of of with + /// size/placeholder values that make it easy to see and design in the editor. + /// + /// + /// A of .
+ /// See for the full list of allowed Types. + /// + /// A new instance of . + /// Thrown if is not a subclass of . + /// Delegates to , for types supported by that method. + [Obsolete( "Migrate to using generic Create method" )] + public static View Create( Type requestedType ) { - var tabView = new TabView + return requestedType switch { - Width = 50, - Height = 5, + null => throw new ArgumentNullException( nameof( requestedType ) ), + { } t when t.IsAssignableTo( typeof( TableView ) ) => Create( ), + { } t when t.IsAssignableTo( typeof( TabView ) ) => Create( ), + { } t when t.IsAssignableTo( typeof( RadioGroup ) ) => Create( ), + { } t when t.IsAssignableTo( typeof( MenuBar ) ) => Create( ), + { } t when t.IsAssignableTo( typeof( StatusBar ) ) => Create( ), + { } t when t == typeof( TextValidateField ) => Create( ), + { } t when t == typeof( ProgressBar ) => Create( ), + { } t when t == typeof( View ) => Create( ), + { } t when t == typeof( Window ) => Create( ), + { } t when t == typeof( TextField ) => Create( ), + { } t when t.IsAssignableTo( typeof( GraphView ) ) => Create( ), + { } t when t.IsAssignableTo( typeof( ListView ) ) => Create( ), + { } t when t == typeof( LineView ) => Create( ), + { } t when t == typeof( TreeView ) => Create( ), + { } t when t == typeof( ScrollView ) => Create( ), + { } t when t.IsAssignableTo( typeof( SpinnerView ) ) => Create( ), + { } t when t.IsAssignableTo( typeof( FrameView ) ) => Create( ), + { } t when t.IsAssignableTo( typeof( HexView ) ) => Create( ), + _ => ReflectionHelpers.GetDefaultViewInstance( requestedType ) }; - - tabView.AddEmptyTab("Tab1"); - tabView.AddEmptyTab("Tab2"); - - return tabView; } - - /// - /// - /// adds a new top level menu (e.g. File, Edit etc). In the designer - /// all menus must have at least 1 under them so it will be - /// created with a single in it already. That item will - /// bear this text. - /// - /// - /// This string should be used by any other areas of code that want to create new under - /// a top/sub menu (e.g. ). - /// - /// - public const string DefaultMenuItemText = "Edit Me"; } From fbf79e4831d13684c4ea28a756fb18780457309e Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 18:00:20 -0700 Subject: [PATCH 08/76] The ReplaceLineEndings function makes all this a lot easier! --- tests/KeyMapTests.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/KeyMapTests.cs b/tests/KeyMapTests.cs index edcce195..447b7aac 100644 --- a/tests/KeyMapTests.cs +++ b/tests/KeyMapTests.cs @@ -1,4 +1,5 @@ using System.IO; +using Microsoft.VisualBasic.CompilerServices; using Terminal.Gui; using TerminalGuiDesigner.UI; @@ -98,13 +99,9 @@ public void DefaultKeyMapFile_NotChanged( ) Assert.That( defaultKeyMapFileName, Does.Exist ); Assert.That( defaultKeyMapFileName, Is.Not.Empty ); - - FileInfo defaultKeyMapFileInfo = new( defaultKeyMapFileName ); - - Assert.That( defaultKeyMapFileInfo, Has.Length.EqualTo( 921 ) ); - - Assert.That( File.ReadAllText( defaultKeyMapFileName ), - Is.EqualTo( ExpectedKeysYamlContent ), + + Assert.That( File.ReadAllText( defaultKeyMapFileName ).ReplaceLineEndings( ), + Is.EqualTo( ExpectedKeysYamlContent.ReplaceLineEndings() ), "Content of Keys.yaml, including newline style, must match expected input." ); } From 7b251210e7af9d2dfea55729f12b8a2a35d43d4a Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 18:01:11 -0700 Subject: [PATCH 09/76] Don't need this any more after the previous commit --- .editorconfig | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index de2bfd3a..d27ace4f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1,3 @@ root = true -[*Keys.yaml] -end_of_line = lf -[*KeyMapTests.cs] -end_of_line = lf \ No newline at end of file From 370c38754a1aa198e65c0ed14deb74a8e1a64eae Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 18:39:32 -0700 Subject: [PATCH 10/76] Use a switch expression to condense this (no behavior change) --- src/PosExtensions.cs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/PosExtensions.cs b/src/PosExtensions.cs index fd5d708a..346f73b8 100644 --- a/src/PosExtensions.cs +++ b/src/PosExtensions.cs @@ -415,23 +415,14 @@ public static bool GetPosType(this Pos? p, IList knownDesigns, out PosTy // It's a Terminal.Gui issue, but we can probably work around it. public static Pos CreatePosRelative(this Design relativeTo, Side side, int offset) { - Pos pos; - switch (side) + Pos pos = side switch { - case Side.Top: - pos = Pos.Top(relativeTo.View); - break; - case Side.Bottom: - pos = Pos.Bottom(relativeTo.View); - break; - case Side.Left: - pos = Pos.Left(relativeTo.View); - break; - case Side.Right: - pos = Pos.Right(relativeTo.View); - break; - default: throw new ArgumentOutOfRangeException(nameof(side)); - } + Side.Top => Pos.Top( relativeTo.View ), + Side.Bottom => Pos.Bottom( relativeTo.View ), + Side.Left => Pos.Left( relativeTo.View ), + Side.Right => Pos.Right( relativeTo.View ), + _ => throw new ArgumentOutOfRangeException( nameof( side ) ) + }; if (offset != 0) { From d05a8b0d79e107c5f220d3d8333e762d0b473770 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 18:46:06 -0700 Subject: [PATCH 11/76] Apply offsets in-line and adjust bug note to be more precise (non-change) --- src/PosExtensions.cs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/PosExtensions.cs b/src/PosExtensions.cs index 346f73b8..6e41e530 100644 --- a/src/PosExtensions.cs +++ b/src/PosExtensions.cs @@ -411,25 +411,18 @@ public static bool GetPosType(this Pos? p, IList knownDesigns, out PosTy /// The offset if any to use e.g. if you want: /// Pos.Left(myView) + 5 /// The resulting of the invoked method (e.g. . - // BUG: This returns absolute positions when offsets are applied + // BUG: This returns absolute positions when offsets are non-zero // It's a Terminal.Gui issue, but we can probably work around it. - public static Pos CreatePosRelative(this Design relativeTo, Side side, int offset) + public static Pos CreatePosRelative(this Design relativeTo, Side side, int offset = 0) { - Pos pos = side switch + return side switch { - Side.Top => Pos.Top( relativeTo.View ), - Side.Bottom => Pos.Bottom( relativeTo.View ), - Side.Left => Pos.Left( relativeTo.View ), - Side.Right => Pos.Right( relativeTo.View ), + Side.Top => Pos.Top( relativeTo.View ) + offset, + Side.Bottom => Pos.Bottom( relativeTo.View ) + offset, + Side.Left => Pos.Left( relativeTo.View ) + offset, + Side.Right => Pos.Right( relativeTo.View ) + offset, _ => throw new ArgumentOutOfRangeException( nameof( side ) ) }; - - if (offset != 0) - { - return pos + offset; - } - - return pos; } private static bool IsRelative(this Pos? p, out Pos posView) From 3495d76c08fd92622163fb24b2729890d6027c94 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 19:17:40 -0700 Subject: [PATCH 12/76] Cover un-covered method --- src/ColorSchemeManager.cs | 2 +- tests/ColorSchemeTests.cs | 49 ++++++++++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/ColorSchemeManager.cs b/src/ColorSchemeManager.cs index af1cf985..0e6e950d 100644 --- a/src/ColorSchemeManager.cs +++ b/src/ColorSchemeManager.cs @@ -61,7 +61,7 @@ public void Remove(NamedColorScheme toDelete) /// schemes. /// /// View to find color schemes in, must be the root design (i.e. ). - /// Thrown if passed a non root . + /// Thrown if passed a non-root . public void FindDeclaredColorSchemes(Design viewBeingEdited) { if (!viewBeingEdited.IsRoot) diff --git a/tests/ColorSchemeTests.cs b/tests/ColorSchemeTests.cs index 9e1e34e0..591cf191 100644 --- a/tests/ColorSchemeTests.cs +++ b/tests/ColorSchemeTests.cs @@ -1,19 +1,54 @@ -using System; using System.IO; using System.Linq; -using Terminal.Gui; -using TerminalGuiDesigner; using TerminalGuiDesigner.Operations; using TerminalGuiDesigner.ToCode; using Attribute = Terminal.Gui.Attribute; namespace UnitTests; +[TestFixture] +[TestOf(typeof(ColorSchemeManager))] +[Category("Core")] internal class ColorSchemeTests : Tests { - [TestCase(true)] - [TestCase(false)] - public void TestHasColorScheme(bool whenMultiSelected) + [Test] + public void RenameScheme( ) + { + var window = new Window( ); + var d = new Design( new SourceCodeFile( new FileInfo( "TenByTen.cs" ) ), Design.RootDesignName, window ); + window.Data = d; + + var state = Application.Begin( window ); + + Assume.That( d.View.ColorScheme, Is.Not.Null.And.SameAs( Colors.Base ) ); + Assume.That( d.HasKnownColorScheme( ), Is.False ); + + var scheme = new ColorScheme( ); + var prop = new SetPropertyOperation( d, d.GetDesignableProperty( nameof( View.ColorScheme ) ) + ?? throw new Exception( "Expected Property did not exist or was not designable" ), null, scheme ); + + prop.Do( ); + + // we still don't know about this scheme yet + Assume.That( d.HasKnownColorScheme( ), Is.False ); + + const string oldName = "fff"; + ColorSchemeManager.Instance.AddOrUpdateScheme( oldName, scheme, d ); + var originalNamedColorScheme = ColorSchemeManager.Instance.GetNamedColorScheme( oldName ); + Assume.That( d.HasKnownColorScheme ); + + // Now rename it and verify + + const string newName = "FancyNewName"; + ColorSchemeManager.Instance.RenameScheme( oldName, newName ); + Assert.That( d.HasKnownColorScheme ); + NamedColorScheme renamedColorScheme = ColorSchemeManager.Instance.GetNamedColorScheme( newName ); + Assert.That( renamedColorScheme, Is.SameAs( originalNamedColorScheme ) ); + Assert.That( renamedColorScheme.Name, Is.EqualTo( newName ) ); + } + + [Test] + public void HasColorScheme([Values]bool whenMultiSelected) { var window = new Window(); var d = new Design(new SourceCodeFile(new FileInfo("TenByTen.cs")), Design.RootDesignName, window); @@ -177,6 +212,7 @@ public void TestColorSchemeProperty_ToString_SelectThenSetScheme() /// /// [Test] + [Category( "Code Generation" )] public void TestColorScheme_RoundTrip([Values]bool multiSelectBeforeSaving) { var mgr = ColorSchemeManager.Instance; @@ -228,6 +264,7 @@ public void TestDefaultColors() } [Test] + [Category( "Code Generation" )] public void TestEditingSchemeAfterLoad([Values]bool withSelection) { var scheme = new ColorScheme(); From a0d3874f4c141509956c6595b49c1e1cb4f636ed Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 19:17:53 -0700 Subject: [PATCH 13/76] Allow this to proceed with warning --- tests/PosTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/PosTests.cs b/tests/PosTests.cs index bb22ac80..82094333 100644 --- a/tests/PosTests.cs +++ b/tests/PosTests.cs @@ -432,7 +432,6 @@ public bool IsCenter( Pos? testValue ) if ( testValue is null ) { Assert.Warn( "BUG: Null returns true for this, when it shouldn't" ); - Assert.Ignore( "BUG: Null returns true for this, when it shouldn't" ); } return testValue.IsCenter( ); } From 0463119c3b76656ec36a1e93b2272d1a1fa905a9 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 19:18:15 -0700 Subject: [PATCH 14/76] Condense these to a switch --- src/Design.cs | 69 ++++++++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/src/Design.cs b/src/Design.cs index 10aada35..f346467a 100644 --- a/src/Design.cs +++ b/src/Design.cs @@ -390,47 +390,54 @@ public IEnumerable GetExtraOperations(Point pos) yield return new DeleteViewOperation(this); - if (this.View is TabView tabView) + switch ( this.View ) { - yield return new AddTabOperation(this, null); - - if (tabView.SelectedTab != null) + case TabView tabView: { - yield return new RemoveTabOperation(this, tabView.SelectedTab); - yield return new RenameTabOperation(this, tabView.SelectedTab, null); - yield return new MoveTabOperation(this, tabView.SelectedTab, -1); - yield return new MoveTabOperation(this, tabView.SelectedTab, 1); + yield return new AddTabOperation(this, null); + + if (tabView.SelectedTab != null) + { + yield return new RemoveTabOperation(this, tabView.SelectedTab); + yield return new RenameTabOperation(this, tabView.SelectedTab, null); + yield return new MoveTabOperation(this, tabView.SelectedTab, -1); + yield return new MoveTabOperation(this, tabView.SelectedTab, 1); + } + + break; } - } + case MenuBar mb: + { + yield return new AddMenuOperation(this, null); - if (this.View is MenuBar mb) - { - yield return new AddMenuOperation(this, null); + var menu = pos.IsEmpty ? mb.GetSelectedMenuItem() : mb.ScreenToMenuBarItem(pos.X); - var menu = pos.IsEmpty ? mb.GetSelectedMenuItem() : mb.ScreenToMenuBarItem(pos.X); + if (menu != null) + { + yield return new RemoveMenuOperation(this, menu); + yield return new RenameMenuOperation(this, menu, null); + yield return new MoveMenuOperation(this, menu, -1); + yield return new MoveMenuOperation(this, menu, 1); + } - if (menu != null) - { - yield return new RemoveMenuOperation(this, menu); - yield return new RenameMenuOperation(this, menu, null); - yield return new MoveMenuOperation(this, menu, -1); - yield return new MoveMenuOperation(this, menu, 1); + break; } - } + case StatusBar sb: + { + yield return new AddStatusItemOperation(this, null); - if (this.View is StatusBar sb) - { - yield return new AddStatusItemOperation(this, null); + var item = sb.ScreenToMenuBarItem(pos.X); - var item = sb.ScreenToMenuBarItem(pos.X); + if (item != null) + { + yield return new RemoveStatusItemOperation(this, item); + yield return new RenameStatusItemOperation(this, item, null); + yield return new SetShortcutOperation(this, item, null); + yield return new MoveStatusItemOperation(this, item, -1); + yield return new MoveStatusItemOperation(this, item, 1); + } - if (item != null) - { - yield return new RemoveStatusItemOperation(this, item); - yield return new RenameStatusItemOperation(this, item, null); - yield return new SetShortcutOperation(this, item, null); - yield return new MoveStatusItemOperation(this, item, -1); - yield return new MoveStatusItemOperation(this, item, 1); + break; } } } From c91fd17e5c9913ba37093a169851e880ead58060 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 19:21:11 -0700 Subject: [PATCH 15/76] Just a note --- src/DimExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/DimExtensions.cs b/src/DimExtensions.cs index 798d8133..07a9c951 100644 --- a/src/DimExtensions.cs +++ b/src/DimExtensions.cs @@ -237,6 +237,8 @@ public static bool GetDimType(this Dim d, out DimType type, out float value, out { if (!d.GetDimType(out var type, out var val, out var offset)) { + // TODO: This is currently unreachable (though a TG change could make it not so) + // Would it maybe be a better idea to throw an exception, so generated code isn't silently incorrect? // could not determine the type return null; } From 5654e4c5f8be99998975cb7f347cfe0d534bf4e2 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 22:53:55 -0700 Subject: [PATCH 16/76] Might as well make these global too. --- tests/LabelTests.cs | 6 ------ tests/UnitTests.csproj | 6 +++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/LabelTests.cs b/tests/LabelTests.cs index 8a5acf14..0a9923fc 100644 --- a/tests/LabelTests.cs +++ b/tests/LabelTests.cs @@ -1,9 +1,3 @@ -using System.IO; -using System.Linq; -using Terminal.Gui; -using TerminalGuiDesigner.Operations; -using TerminalGuiDesigner.ToCode; - namespace UnitTests; internal class LabelTests : Tests diff --git a/tests/UnitTests.csproj b/tests/UnitTests.csproj index 64bc39c0..e21ebf65 100644 --- a/tests/UnitTests.csproj +++ b/tests/UnitTests.csproj @@ -1,4 +1,4 @@ - + net7.0;net8.0 @@ -35,9 +35,13 @@ + + + + From 5b0b557443c0c1bece4660587e79769f8d35633b Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 22:54:29 -0700 Subject: [PATCH 17/76] Annotate the fixture --- tests/LabelTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/LabelTests.cs b/tests/LabelTests.cs index 0a9923fc..771b7ac6 100644 --- a/tests/LabelTests.cs +++ b/tests/LabelTests.cs @@ -1,5 +1,10 @@ namespace UnitTests; +[TestFixture] +[TestOf( typeof( OperationManager ) )] +[TestOf( typeof( Label ) )] +[Category( "Core" )] +[Category( "UI" )] internal class LabelTests : Tests { [Test] From ecbf658f4c21ca5b0073756d1391407fa833be93 Mon Sep 17 00:00:00 2001 From: Brandon Thetford Date: Wed, 20 Dec 2023 23:02:45 -0700 Subject: [PATCH 18/76] Convert this test and add pre-conditions This isn't really a test of labels. It's an OperationManager test, performed on a label. --- tests/LabelTests.cs | 52 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/tests/LabelTests.cs b/tests/LabelTests.cs index 771b7ac6..72e4b7c1 100644 --- a/tests/LabelTests.cs +++ b/tests/LabelTests.cs @@ -1,4 +1,4 @@ -namespace UnitTests; +namespace UnitTests; [TestFixture] [TestOf( typeof( OperationManager ) )] @@ -8,7 +8,7 @@ namespace UnitTests; internal class LabelTests : Tests { [Test] - public void Test_ChangingLabelX() + public void ChangingLabelProperty([Values("X")]string propertyName) { var file = new FileInfo("Test_ChangingLabelX.cs"); var viewToCode = new ViewToCode(); @@ -17,20 +17,54 @@ public void Test_ChangingLabelX() var op = new AddViewOperation(new Label("Hello World"), designOut, "myLabel"); op.Do(); + Assume.That( designOut, Is.Not.Null.And.InstanceOf( ) ); + Assume.That( designOut.View, Is.Not.Null.And.InstanceOf( ) ); + // the Hello world label - var lblDesign = designOut.GetAllDesigns().Single(d => d.View is Label); - var xProp = lblDesign.GetDesignableProperties().Single(p => p.PropertyInfo.Name.Equals("X")); + var lblDesign = designOut.GetAllDesigns( ).SingleOrDefault( d => d.View is Label ); + Assume.That( lblDesign, Is.Not.Null.And.InstanceOf( ) ); + Assume.That( lblDesign!.View, Is.Not.Null.And.InstanceOf