diff --git a/NativeAot/Program.cs b/NativeAot/Program.cs index 5ef38db05..4aae3c099 100644 --- a/NativeAot/Program.cs +++ b/NativeAot/Program.cs @@ -17,14 +17,14 @@ public static class Program #region The code in this region is not intended for use in a native Aot self-contained. It's just here to make sure there is no functionality break with localization in Terminal.Gui using self-contained - if (Equals(Thread.CurrentThread.CurrentUICulture, CultureInfo.InvariantCulture) && Application.SupportedCultures.Count == 0) + if (Equals(Thread.CurrentThread.CurrentUICulture, CultureInfo.InvariantCulture) && Application.SupportedCultures!.Count == 0) { // Only happens if the project has true Debug.Assert (Application.SupportedCultures.Count == 0); } else { - Debug.Assert (Application.SupportedCultures.Count > 0); + Debug.Assert (Application.SupportedCultures!.Count > 0); Debug.Assert (Equals (CultureInfo.CurrentCulture, Thread.CurrentThread.CurrentUICulture)); } diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs index 675d78c6f..4653f5419 100644 --- a/Terminal.Gui/Application/Application.Keyboard.cs +++ b/Terminal.Gui/Application/Application.Keyboard.cs @@ -190,7 +190,7 @@ public static partial class Application // Keyboard handling foreach (Command command in appBinding.Commands) { - if (!CommandImplementations.ContainsKey (command)) + if (!CommandImplementations!.ContainsKey (command)) { throw new NotSupportedException ( @$"A KeyBinding was set up for the command {command} ({keyEvent}) but that command is not supported by Application." @@ -274,7 +274,7 @@ public static partial class Application // Keyboard handling /// /// Commands for Application. /// - private static Dictionary> CommandImplementations { get; set; } + private static Dictionary>? CommandImplementations { get; set; } /// /// @@ -292,7 +292,7 @@ public static partial class Application // Keyboard handling /// /// The command. /// The function. - private static void AddCommand (Command command, Func f) { CommandImplementations [command] = ctx => f (); } + private static void AddCommand (Command command, Func f) { CommandImplementations! [command] = ctx => f (); } static Application () { AddApplicationKeyBindings (); } @@ -451,31 +451,4 @@ public static partial class Application // Keyboard handling .Distinct () .ToList (); } - - ///// - ///// Gets the list of Views that have key bindings for the specified key. - ///// - ///// - ///// This is an internal method used by the class to add Application key bindings. - ///// - ///// The key to check. - ///// Outputs the list of views bound to - ///// if successful. - //internal static bool TryGetKeyBindings (Key key, out List views) { return _keyBindings.TryGetValue (key, out views); } - - /// - /// Removes all scoped key bindings for the specified view. - /// - /// - /// This is an internal method used by the class to remove Application key bindings. - /// - /// The view that is bound to the key. - internal static void RemoveKeyBindings (View view) - { - List list = KeyBindings.Bindings - .Where (kv => kv.Value.Scope != KeyBindingScope.Application) - .Select (kv => kv.Value) - .Distinct () - .ToList (); - } } diff --git a/Terminal.Gui/Application/Application.Mouse.cs b/Terminal.Gui/Application/Application.Mouse.cs index 2c497b761..3af3808b2 100644 --- a/Terminal.Gui/Application/Application.Mouse.cs +++ b/Terminal.Gui/Application/Application.Mouse.cs @@ -160,13 +160,13 @@ public static partial class Application // Mouse handling Position = frameLoc, Flags = mouseEvent.Flags, ScreenPosition = mouseEvent.Position, - View = MouseGrabView + View = view ?? MouseGrabView }; if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false) { // The mouse has moved outside the bounds of the view that grabbed the mouse - MouseEnteredView?.NewMouseLeaveEvent (mouseEvent); + MouseGrabView?.NewMouseLeaveEvent (mouseEvent); } //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 0ef0facba..d37ba3513 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -32,7 +32,7 @@ public static partial class Application /// A string representation of the Application public new static string ToString () { - ConsoleDriver driver = Driver; + ConsoleDriver? driver = Driver; if (driver is null) { @@ -47,13 +47,17 @@ public static partial class Application /// /// The driver to use to render the contents. /// A string representation of the Application - public static string ToString (ConsoleDriver driver) + public static string ToString (ConsoleDriver? driver) { + if (driver is null) + { + return string.Empty; + } var sb = new StringBuilder (); - Cell [,] contents = driver.Contents; + Cell [,] contents = driver?.Contents!; - for (var r = 0; r < driver.Rows; r++) + for (var r = 0; r < driver!.Rows; r++) { for (var c = 0; c < driver.Cols; c++) { diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 8585ccb38..d96ab73ea 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -61,7 +61,7 @@ public record struct Thickness [JsonInclude] public int Bottom { - get => (int)_sides.W; + readonly get => (int)_sides.W; set => _sides.W = value; } @@ -249,7 +249,7 @@ public record struct Thickness [JsonInclude] public int Left { - get => (int)_sides.X; + readonly get => (int)_sides.X; set => _sides.X = value; } @@ -265,7 +265,7 @@ public record struct Thickness [JsonInclude] public int Right { - get => (int)_sides.Z; + readonly get => (int)_sides.Z; set => _sides.Z = value; } @@ -273,7 +273,7 @@ public record struct Thickness [JsonInclude] public int Top { - get => (int)_sides.Y; + readonly get => (int)_sides.Y; set => _sides.Y = value; } diff --git a/Terminal.Gui/View/Layout/Dim.cs b/Terminal.Gui/View/Layout/Dim.cs index 3db2be5f9..cfb286b6c 100644 --- a/Terminal.Gui/View/Layout/Dim.cs +++ b/Terminal.Gui/View/Layout/Dim.cs @@ -1,8 +1,8 @@ #nullable enable -using System.Diagnostics; - namespace Terminal.Gui; +using System.Numerics; + /// /// /// A Dim object describes the dimensions of a . Dim is the type of the @@ -78,7 +78,7 @@ namespace Terminal.Gui; /// /// /// -public abstract class Dim +public abstract record Dim : IEqualityOperators { #region static Dim creation methods @@ -113,12 +113,10 @@ public abstract class Dim /// The maximum dimension the View's ContentSize will be fit to. public static Dim? Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim? minimumContentDim = null, Dim? maximumContentDim = null) { - return new DimAuto () - { - MinimumContentDim = minimumContentDim, - MaximumContentDim = maximumContentDim, - Style = style - }; + return new DimAuto ( + MinimumContentDim: minimumContentDim, + MaximumContentDim: maximumContentDim, + Style: style); } /// @@ -172,28 +170,22 @@ public abstract class Dim #endregion static Dim creation methods + /// - /// Indicates whether the specified type is in the hierarchy of this Dim object. + /// Indicates whether the specified type is in the hierarchy of this Dim object. /// - /// - /// + /// A reference to this instance. /// - public bool Has (Type type, out Dim dim) + public bool Has (out Dim dim) where T : Dim { dim = this; - if (type == GetType ()) - { - return true; - } - // If we are a PosCombine, we have to check the left and right - // to see if they are of the type we are looking for. - if (this is DimCombine { } combine && (combine.Left.Has (type, out dim) || combine.Right.Has (type, out dim))) - { - return true; - } - - return false; + return this switch + { + DimCombine combine => combine.Left.Has (out dim) || combine.Right.Has (out dim), + T => true, + _ => false + }; } #region virtual methods @@ -208,7 +200,7 @@ public abstract class Dim /// subclass of Dim that is used. For example, DimAbsolute returns a fixed dimension, DimFactor returns a /// dimension that is a certain percentage of the super view's size, and so on. /// - internal virtual int GetAnchor (int size) { return 0; } + internal abstract int GetAnchor (int size); /// /// Calculates and returns the dimension of a object. It takes into account the location of the @@ -228,7 +220,7 @@ public abstract class Dim /// internal virtual int Calculate (int location, int superviewContentSize, View us, Dimension dimension) { - return Math.Max (GetAnchor (superviewContentSize - location), 0); + return Math.Clamp (GetAnchor (superviewContentSize - location), 0, short.MaxValue); } /// diff --git a/Terminal.Gui/View/Layout/DimAbsolute.cs b/Terminal.Gui/View/Layout/DimAbsolute.cs index 72d4e12f7..cb078a121 100644 --- a/Terminal.Gui/View/Layout/DimAbsolute.cs +++ b/Terminal.Gui/View/Layout/DimAbsolute.cs @@ -10,19 +10,13 @@ namespace Terminal.Gui; /// methods on the class to create objects instead. /// /// -/// -public class DimAbsolute (int size) : Dim +/// +public record DimAbsolute (int Size) : Dim { - /// - public override bool Equals (object? other) { return other is DimAbsolute abs && abs.Size == Size; } - - /// - public override int GetHashCode () { return Size.GetHashCode (); } - /// /// Gets the size of the dimension. /// - public int Size { get; } = size; + public int Size { get; } = Size; /// public override string ToString () { return $"Absolute({Size})"; } diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs index 6bd5f4d0f..e552d1028 100644 --- a/Terminal.Gui/View/Layout/DimAuto.cs +++ b/Terminal.Gui/View/Layout/DimAuto.cs @@ -15,64 +15,17 @@ namespace Terminal.Gui; /// methods on the class to create objects instead. /// /// -public class DimAuto : Dim +/// The maximum dimension the View's ContentSize will be fit to. +/// The minimum dimension the View's ContentSize will be constrained to. +/// The of the . +public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoStyle Style) : Dim { - private readonly Dim? _maximumContentDim; - - private readonly Dim? _minimumContentDim; - - private readonly DimAutoStyle _style; - - /// - public override bool Equals (object? other) - { - if (other is not DimAuto auto) - { - return false; - } - - return auto.MinimumContentDim == MinimumContentDim && auto.MaximumContentDim == MaximumContentDim && auto.Style == Style; - } - - /// - public override int GetHashCode () { return HashCode.Combine (MinimumContentDim, MaximumContentDim, Style); } - - /// - /// Gets the maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED. - /// - - // ReSharper disable once ConvertToAutoProperty - public required Dim? MaximumContentDim - { - get => _maximumContentDim; - init => _maximumContentDim = value; - } - - /// - /// Gets the minimum dimension the View's ContentSize will be constrained to. - /// - - // ReSharper disable once ConvertToAutoProperty - public required Dim? MinimumContentDim - { - get => _minimumContentDim; - init => _minimumContentDim = value; - } - - /// - /// Gets the style of the DimAuto. - /// - - // ReSharper disable once ConvertToAutoProperty - public required DimAutoStyle Style - { - get => _style; - init => _style = value; - } - /// public override string ToString () { return $"Auto({Style},{MinimumContentDim},{MaximumContentDim})"; } + /// + internal override int GetAnchor (int size) => 0; + internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension) { var textSize = 0; @@ -181,8 +134,8 @@ public class DimAuto : Dim && !v.X.Has (typeof (PosAnchorEnd), out _) && !v.X.Has (typeof (PosAlign), out _) && !v.X.Has (typeof (PosCenter), out _) - && !v.Width.Has (typeof (DimFill), out _) - && !v.Width.Has (typeof (DimPercent), out _) + && !v.Width.Has (out _) + && !v.Width.Has (out _) ) .ToList (); } @@ -194,8 +147,8 @@ public class DimAuto : Dim && !v.Y.Has (typeof (PosAnchorEnd), out _) && !v.Y.Has (typeof (PosAlign), out _) && !v.Y.Has (typeof (PosCenter), out _) - && !v.Height.Has (typeof (DimFill), out _) - && !v.Height.Has (typeof (DimPercent), out _) + && !v.Height.Has (out _) + && !v.Height.Has (out _) ) .ToList (); } @@ -419,11 +372,11 @@ public class DimAuto : Dim if (dimension == Dimension.Width) { - dimViewSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has (typeof (DimView), out _)).ToList (); + dimViewSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has (out _)).ToList (); } else { - dimViewSubViews = includedSubviews.Where (v => v.Height is { } && v.Height.Has (typeof (DimView), out _)).ToList (); + dimViewSubViews = includedSubviews.Where (v => v.Height is { } && v.Height.Has (out _)).ToList (); } for (var i = 0; i < dimViewSubViews.Count; i++) diff --git a/Terminal.Gui/View/Layout/DimCombine.cs b/Terminal.Gui/View/Layout/DimCombine.cs index f8b5d141c..7a5d23614 100644 --- a/Terminal.Gui/View/Layout/DimCombine.cs +++ b/Terminal.Gui/View/Layout/DimCombine.cs @@ -4,31 +4,31 @@ namespace Terminal.Gui; /// /// Represents a dimension that is a combination of two other dimensions. /// -/// +/// /// Indicates whether the two dimensions are added or subtracted. /// /// /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// -/// The left dimension. -/// The right dimension. -public class DimCombine (AddOrSubtract add, Dim left, Dim right) : Dim +/// The left dimension. +/// The right dimension. +public record DimCombine (AddOrSubtract Add, Dim Left, Dim Right) : Dim { /// /// Gets whether the two dimensions are added or subtracted. /// - public AddOrSubtract Add { get; } = add; + public AddOrSubtract Add { get; } = Add; /// /// Gets the left dimension. /// - public Dim Left { get; } = left; + public Dim Left { get; } = Left; /// /// Gets the right dimension. /// - public Dim Right { get; } = right; + public Dim Right { get; } = Right; /// public override string ToString () { return $"Combine({Left}{(Add == AddOrSubtract.Add ? '+' : '-')}{Right})"; } diff --git a/Terminal.Gui/View/Layout/DimFill.cs b/Terminal.Gui/View/Layout/DimFill.cs index 03cf6f3d2..7550b87db 100644 --- a/Terminal.Gui/View/Layout/DimFill.cs +++ b/Terminal.Gui/View/Layout/DimFill.cs @@ -8,20 +8,9 @@ namespace Terminal.Gui; /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// -/// The margin to not fill. -public class DimFill (int margin) : Dim +/// The margin to not fill. +public record DimFill (int Margin) : Dim { - /// - public override bool Equals (object? other) { return other is DimFill fill && fill.Margin == Margin; } - - /// - public override int GetHashCode () { return Margin.GetHashCode (); } - - /// - /// Gets the margin to not fill. - /// - public int Margin { get; } = margin; - /// public override string ToString () { return $"Fill({Margin})"; } diff --git a/Terminal.Gui/View/Layout/DimFunc.cs b/Terminal.Gui/View/Layout/DimFunc.cs index c15e9fc8c..c51406f40 100644 --- a/Terminal.Gui/View/Layout/DimFunc.cs +++ b/Terminal.Gui/View/Layout/DimFunc.cs @@ -2,28 +2,22 @@ namespace Terminal.Gui; /// -/// Represents a function object that computes the dimension by executing the provided function. +/// Represents a function object that computes the dimension by executing the provided function. /// /// /// This is a low-level API that is typically used internally by the layout system. Use the various static -/// methods on the class to create objects instead. +/// methods on the class to create objects instead. /// -/// -public class DimFunc (Func dim) : Dim +/// The function that computes the dimension. +public record DimFunc (Func Fn) : Dim { - /// - public override bool Equals (object? other) { return other is DimFunc f && f.Func () == Func (); } - /// /// Gets the function that computes the dimension. /// - public new Func Func { get; } = dim; + public Func Fn { get; } = Fn; /// - public override int GetHashCode () { return Func.GetHashCode (); } + public override string ToString () { return $"DimFunc({Fn ()})"; } - /// - public override string ToString () { return $"DimFunc({Func ()})"; } - - internal override int GetAnchor (int size) { return Func (); } + internal override int GetAnchor (int size) { return Fn (); } } \ No newline at end of file diff --git a/Terminal.Gui/View/Layout/DimPercent.cs b/Terminal.Gui/View/Layout/DimPercent.cs index 2ae81302a..e94be76a7 100644 --- a/Terminal.Gui/View/Layout/DimPercent.cs +++ b/Terminal.Gui/View/Layout/DimPercent.cs @@ -8,35 +8,24 @@ namespace Terminal.Gui; /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// -/// The percentage. -/// +/// The percentage. +/// /// If the dimension is computed using the View's position ( or /// ); otherwise, the dimension is computed using the View's . /// -public class DimPercent (int percent, DimPercentMode mode = DimPercentMode.ContentSize) : Dim +public record DimPercent (int Percentage, DimPercentMode Mode = DimPercentMode.ContentSize) : Dim { - /// - public override bool Equals (object? other) { return other is DimPercent f && f.Percent == Percent && f.Mode == Mode; } - - /// - public override int GetHashCode () { return Percent.GetHashCode (); } - - /// - /// Gets the percentage. - /// - public new int Percent { get; } = percent; - /// /// /// - public override string ToString () { return $"Percent({Percent},{Mode})"; } + public override string ToString () { return $"Percent({Percentage},{Mode})"; } /// /// Gets whether the dimension is computed using the View's position or GetContentSize (). /// - public DimPercentMode Mode { get; } = mode; + public DimPercentMode Mode { get; } = Mode; - internal override int GetAnchor (int size) { return (int)(size * (Percent / 100f)); } + internal override int GetAnchor (int size) { return (int)(size * (Percentage / 100f)); } internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension) { diff --git a/Terminal.Gui/View/Layout/DimView.cs b/Terminal.Gui/View/Layout/DimView.cs index 7a7568a95..b37f9bb7e 100644 --- a/Terminal.Gui/View/Layout/DimView.cs +++ b/Terminal.Gui/View/Layout/DimView.cs @@ -8,7 +8,7 @@ namespace Terminal.Gui; /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// -public class DimView : Dim +public record DimView : Dim { /// /// Initializes a new instance of the class. @@ -26,12 +26,6 @@ public class DimView : Dim /// public Dimension Dimension { get; } - /// - public override bool Equals (object? other) { return other is DimView abs && abs.Target == Target && abs.Dimension == Dimension; } - - /// - public override int GetHashCode () { return Target!.GetHashCode (); } - /// /// Gets the View the dimension is anchored to. /// diff --git a/Terminal.Gui/View/Layout/Pos.cs b/Terminal.Gui/View/Layout/Pos.cs index ba377fcdc..638b13b9e 100644 --- a/Terminal.Gui/View/Layout/Pos.cs +++ b/Terminal.Gui/View/Layout/Pos.cs @@ -131,7 +131,7 @@ namespace Terminal.Gui; /// /// /// -public abstract class Pos +public abstract record Pos { #region static Pos creation methods diff --git a/Terminal.Gui/View/Layout/PosAbsolute.cs b/Terminal.Gui/View/Layout/PosAbsolute.cs index 44afdac97..b71da5013 100644 --- a/Terminal.Gui/View/Layout/PosAbsolute.cs +++ b/Terminal.Gui/View/Layout/PosAbsolute.cs @@ -10,19 +10,13 @@ namespace Terminal.Gui; /// methods on the class to create objects instead. /// /// -/// -public class PosAbsolute (int position) : Pos +/// +public record PosAbsolute (int Position) : Pos { /// /// The position of the in the layout. /// - public int Position { get; } = position; - - /// - public override bool Equals (object? other) { return other is PosAbsolute abs && abs.Position == Position; } - - /// - public override int GetHashCode () { return Position.GetHashCode (); } + public int Position { get; } = Position; /// public override string ToString () { return $"Absolute({Position})"; } diff --git a/Terminal.Gui/View/Layout/PosAlign.cs b/Terminal.Gui/View/Layout/PosAlign.cs index 79e22d410..d3873a4c0 100644 --- a/Terminal.Gui/View/Layout/PosAlign.cs +++ b/Terminal.Gui/View/Layout/PosAlign.cs @@ -1,7 +1,6 @@ #nullable enable using System.ComponentModel; -using System.Drawing; namespace Terminal.Gui; @@ -24,19 +23,13 @@ namespace Terminal.Gui; /// The alignment is applied to all views with the same . /// /// -public class PosAlign : Pos +public record PosAlign : Pos { /// /// The cached location. Used to store the calculated location to minimize recalculating it. /// public int? _cachedLocation; - /// - /// Gets the identifier of a set of views that should be aligned together. When only a single - /// set of views in a SuperView is aligned, setting is not needed because it defaults to 0. - /// - public int GroupId { get; init; } - private readonly Aligner? _aligner; /// @@ -57,6 +50,88 @@ public class PosAlign : Pos } } + // TODO: PosAlign.CalculateMinDimension is a hack. Need to figure out a better way of doing this. + /// + /// Returns the minimum size a group of views with the same can be. + /// + /// + /// + /// + /// + public static int CalculateMinDimension (int groupId, IList views, Dimension dimension) + { + List dimensionsList = new (); + + // PERF: If this proves a perf issue, consider caching a ref to this list in each item + List viewsInGroup = views.Where ( + v => + { + return dimension switch + { + Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId, + Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId, + _ => false + }; + }) + .ToList (); + + if (viewsInGroup.Count == 0) + { + return 0; + } + + // PERF: We iterate over viewsInGroup multiple times here. + + // Update the dimensionList with the sizes of the views + for (var index = 0; index < viewsInGroup.Count; index++) + { + View view = viewsInGroup [index]; + + PosAlign? posAlign = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign; + + if (posAlign is { }) + { + dimensionsList.Add (dimension == Dimension.Width ? view.Frame.Width : view.Frame.Height); + } + } + + // Align + return dimensionsList.Sum (); + } + + /// + /// Gets the identifier of a set of views that should be aligned together. When only a single + /// set of views in a SuperView is aligned, setting is not needed because it defaults to 0. + /// + public int GroupId { get; init; } + + /// + public override string ToString () { return $"Align(alignment={Aligner.Alignment},modes={Aligner.AlignmentModes},groupId={GroupId})"; } + + internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension) + { + if (_cachedLocation.HasValue && Aligner.ContainerSize == superviewDimension) + { + return _cachedLocation.Value; + } + + if (us?.SuperView is null) + { + return 0; + } + + AlignAndUpdateGroup (GroupId, us.SuperView.Subviews, dimension, superviewDimension); + + if (_cachedLocation.HasValue) + { + return _cachedLocation.Value; + } + + return 0; + } + + internal override int GetAnchor (int width) { return _cachedLocation ?? 0 - width; } + /// /// Aligns the views in that have the same group ID as . /// Updates each view's cached _location. @@ -71,30 +146,30 @@ public class PosAlign : Pos // PERF: If this proves a perf issue, consider caching a ref to this list in each item List posAligns = views.Select ( - v => - { - switch (dimension) - { - case Dimension.Width when v.X.Has (typeof (PosAlign), out var pos): + v => + { + switch (dimension) + { + case Dimension.Width when v.X.Has (typeof (PosAlign), out Pos pos): - if (pos is PosAlign posAlignX && posAlignX.GroupId == groupId) - { - return posAlignX; - } + if (pos is PosAlign posAlignX && posAlignX.GroupId == groupId) + { + return posAlignX; + } - break; - case Dimension.Height when v.Y.Has (typeof (PosAlign), out var pos): - if (pos is PosAlign posAlignY && posAlignY.GroupId == groupId) - { - return posAlignY; - } + break; + case Dimension.Height when v.Y.Has (typeof (PosAlign), out Pos pos): + if (pos is PosAlign posAlignY && posAlignY.GroupId == groupId) + { + return posAlignY; + } - break; - } + break; + } - return null; - }) - .ToList (); + return null; + }) + .ToList (); // PERF: We iterate over viewsInGroup multiple times here. @@ -136,92 +211,4 @@ public class PosAlign : Pos } private void Aligner_PropertyChanged (object? sender, PropertyChangedEventArgs e) { _cachedLocation = null; } - - /// - public override bool Equals (object? other) - { - return other is PosAlign align - && GroupId == align.GroupId - && align.Aligner.Alignment == Aligner.Alignment - && align.Aligner.AlignmentModes == Aligner.AlignmentModes; - } - - /// - public override int GetHashCode () { return HashCode.Combine (Aligner, GroupId); } - - /// - public override string ToString () { return $"Align(alignment={Aligner.Alignment},modes={Aligner.AlignmentModes},groupId={GroupId})"; } - - internal override int GetAnchor (int width) { return _cachedLocation ?? 0 - width; } - - internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension) - { - if (_cachedLocation.HasValue && Aligner.ContainerSize == superviewDimension) - { - return _cachedLocation.Value; - } - - if (us?.SuperView is null) - { - return 0; - } - - AlignAndUpdateGroup (GroupId, us.SuperView.Subviews, dimension, superviewDimension); - - if (_cachedLocation.HasValue) - { - return _cachedLocation.Value; - } - - return 0; - } - - // TODO: PosAlign.CalculateMinDimension is a hack. Need to figure out a better way of doing this. - /// - /// Returns the minimum size a group of views with the same can be. - /// - /// - /// - /// - /// - public static int CalculateMinDimension (int groupId, IList views, Dimension dimension) - { - List dimensionsList = new (); - - // PERF: If this proves a perf issue, consider caching a ref to this list in each item - List viewsInGroup = views.Where ( - v => - { - return dimension switch - { - Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId, - Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId, - _ => false - }; - }) - .ToList (); - - if (viewsInGroup.Count == 0) - { - return 0; - } - - // PERF: We iterate over viewsInGroup multiple times here. - - // Update the dimensionList with the sizes of the views - for (var index = 0; index < viewsInGroup.Count; index++) - { - View view = viewsInGroup [index]; - - PosAlign? posAlign = dimension == Dimension.Width ? view.X as PosAlign : view.Y as PosAlign; - - if (posAlign is { }) - { - dimensionsList.Add (dimension == Dimension.Width ? view.Frame.Width : view.Frame.Height); - } - } - - // Align - return dimensionsList.Sum (); - } } diff --git a/Terminal.Gui/View/Layout/PosAnchorEnd.cs b/Terminal.Gui/View/Layout/PosAnchorEnd.cs index e4641c2b5..9156e2230 100644 --- a/Terminal.Gui/View/Layout/PosAnchorEnd.cs +++ b/Terminal.Gui/View/Layout/PosAnchorEnd.cs @@ -10,7 +10,7 @@ namespace Terminal.Gui; /// methods on the class to create objects instead. /// /// -public class PosAnchorEnd : Pos +public record PosAnchorEnd : Pos { /// /// Gets the offset of the position from the right/bottom. @@ -30,12 +30,6 @@ public class PosAnchorEnd : Pos /// public PosAnchorEnd (int offset) { Offset = offset; } - /// - public override bool Equals (object? other) { return other is PosAnchorEnd anchorEnd && anchorEnd.Offset == Offset; } - - /// - public override int GetHashCode () { return Offset.GetHashCode (); } - /// /// If true, the offset is the width of the view, if false, the offset is the offset value. /// diff --git a/Terminal.Gui/View/Layout/PosCenter.cs b/Terminal.Gui/View/Layout/PosCenter.cs index 8f3d700b5..1d7cdc1e1 100644 --- a/Terminal.Gui/View/Layout/PosCenter.cs +++ b/Terminal.Gui/View/Layout/PosCenter.cs @@ -4,7 +4,7 @@ namespace Terminal.Gui; /// /// Represents a position that is centered. /// -public class PosCenter : Pos +public record PosCenter : Pos { /// public override string ToString () { return "Center"; } diff --git a/Terminal.Gui/View/Layout/PosCombine.cs b/Terminal.Gui/View/Layout/PosCombine.cs index 63ff1814f..884bb2704 100644 --- a/Terminal.Gui/View/Layout/PosCombine.cs +++ b/Terminal.Gui/View/Layout/PosCombine.cs @@ -10,27 +10,27 @@ namespace Terminal.Gui; /// methods on the class to create objects instead. /// /// -/// +/// /// Indicates whether the two positions are added or subtracted. /// -/// The left position. -/// The right position. -public class PosCombine (AddOrSubtract add, Pos left, Pos right) : Pos +/// The left position. +/// The right position. +public record PosCombine (AddOrSubtract Add, Pos Left, Pos Right) : Pos { /// /// Gets whether the two positions are added or subtracted. /// - public AddOrSubtract Add { get; } = add; + public AddOrSubtract Add { get; } = Add; /// /// Gets the left position. /// - public new Pos Left { get; } = left; + public new Pos Left { get; } = Left; /// /// Gets the right position. /// - public new Pos Right { get; } = right; + public new Pos Right { get; } = Right; /// public override string ToString () { return $"Combine({Left}{(Add == AddOrSubtract.Add ? '+' : '-')}{Right})"; } diff --git a/Terminal.Gui/View/Layout/PosFunc.cs b/Terminal.Gui/View/Layout/PosFunc.cs index 24344f9a8..0bd819ad5 100644 --- a/Terminal.Gui/View/Layout/PosFunc.cs +++ b/Terminal.Gui/View/Layout/PosFunc.cs @@ -4,28 +4,11 @@ namespace Terminal.Gui; /// /// Represents a position that is computed by executing a function that returns an integer position. /// -/// -/// -/// This is a low-level API that is typically used internally by the layout system. Use the various static -/// methods on the class to create objects instead. -/// -/// -/// The position. -public class PosFunc (Func pos) : Pos +/// The function that computes the position. +public record PosFunc (Func Fn) : Pos { - /// - /// Gets the function that computes the position. - /// - public new Func Func { get; } = pos; - /// - public override bool Equals (object? other) { return other is PosFunc f && f.Func () == Func (); } + public override string ToString () { return $"PosFunc({Fn ()})"; } - /// - public override int GetHashCode () { return Func.GetHashCode (); } - - /// - public override string ToString () { return $"PosFunc({Func ()})"; } - - internal override int GetAnchor (int size) { return Func (); } + internal override int GetAnchor (int size) { return Fn (); } } \ No newline at end of file diff --git a/Terminal.Gui/View/Layout/PosPercent.cs b/Terminal.Gui/View/Layout/PosPercent.cs index 384ba1fda..17b99cb69 100644 --- a/Terminal.Gui/View/Layout/PosPercent.cs +++ b/Terminal.Gui/View/Layout/PosPercent.cs @@ -10,19 +10,13 @@ namespace Terminal.Gui; /// methods on the class to create objects instead. /// /// -/// -public class PosPercent (int percent) : Pos +/// +public record PosPercent (int Percent) : Pos { /// /// Gets the percentage of the width or height of the SuperView. /// - public new int Percent { get; } = percent; - - /// - public override bool Equals (object? other) { return other is PosPercent i && i.Percent == Percent; } - - /// - public override int GetHashCode () { return Percent.GetHashCode (); } + public new int Percent { get; } = Percent; /// public override string ToString () { return $"Percent({Percent})"; } diff --git a/Terminal.Gui/View/Layout/PosView.cs b/Terminal.Gui/View/Layout/PosView.cs index a46f6898a..92748d88b 100644 --- a/Terminal.Gui/View/Layout/PosView.cs +++ b/Terminal.Gui/View/Layout/PosView.cs @@ -10,25 +10,19 @@ namespace Terminal.Gui; /// methods on the class to create objects instead. /// /// -/// The View the position is anchored to. -/// The side of the View the position is anchored to. -public class PosView (View? view, Side side) : Pos +/// The View the position is anchored to. +/// The side of the View the position is anchored to. +public record PosView (View? View, Side Side) : Pos { /// /// Gets the View the position is anchored to. /// - public View? Target { get; } = view; + public View? Target { get; } = View; /// /// Gets the side of the View the position is anchored to. /// - public Side Side { get; } = side; - - /// - public override bool Equals (object? other) { return other is PosView abs && abs.Target == Target && abs.Side == Side; } - - /// - public override int GetHashCode () { return Target!.GetHashCode (); } + public Side Side { get; } = Side; /// public override string ToString () diff --git a/Terminal.Gui/View/View.Keyboard.cs b/Terminal.Gui/View/View.Keyboard.cs index 73625417a..7a7fd35c5 100644 --- a/Terminal.Gui/View/View.Keyboard.cs +++ b/Terminal.Gui/View/View.Keyboard.cs @@ -27,7 +27,6 @@ public partial class View // Keyboard APIs private void DisposeKeyboard () { TitleTextFormatter.HotKeyChanged -= TitleTextFormatter_HotKeyChanged; - Application.RemoveKeyBindings (this); } #region HotKey Support diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs index 419f2b56c..c13367df3 100644 --- a/Terminal.Gui/View/View.Layout.cs +++ b/Terminal.Gui/View/View.Layout.cs @@ -474,7 +474,7 @@ public partial class View // Layout APIs return; } - if (_height is { } && _height.Has (typeof (DimAuto), out _)) + if (_height is { } && _height.Has (out _)) { // Reset ContentSize to Viewport _contentSize = null; @@ -523,7 +523,7 @@ public partial class View // Layout APIs return; } - if (_width is { } && _width.Has (typeof (DimAuto), out _)) + if (_width is { } && _width.Has (out _)) { // Reset ContentSize to Viewport _contentSize = null; diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index b05593fda..55f174c34 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -51,9 +51,9 @@ public class Label : View set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value; } - private new bool? FocusNext () + private bool? FocusNext () { - var me = SuperView?.Subviews.IndexOf (this) ?? -1; + int me = SuperView?.Subviews.IndexOf (this) ?? -1; if (me != -1 && me < SuperView?.Subviews.Count - 1) { SuperView?.Subviews [me + 1].SetFocus (); diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index a900d060e..570bf7847 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -1504,8 +1504,7 @@ public class MenuBar : View, IDesignable return false; } } - else if (!_isContextMenuLoading - && !(me.View is MenuBar || me.View is Menu) + else if (!(me.View is MenuBar || me.View is Menu) && me.Flags != MouseFlags.ReportMousePosition && me.Flags != 0) { diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index df57bd6b0..d250b0390 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -100,7 +100,7 @@ public class NumericUpDown : View where T : notnull return false; } - if (Value is { }) + if (Value is { } && Increment is { }) { Value = (dynamic)Value + (dynamic)Increment; } @@ -117,11 +117,12 @@ public class NumericUpDown : View where T : notnull return false; } - if (Value is { }) + if (Value is { } && Increment is { }) { Value = (dynamic)Value - (dynamic)Increment; } + return true; }); @@ -218,23 +219,23 @@ public class NumericUpDown : View where T : notnull Text = _number.Text; } - private T _increment; + private T? _increment; /// /// - public T Increment + public T? Increment { get => _increment; set { - if ((dynamic)_increment == (dynamic)value) + if (_increment is { } && value is { } && (dynamic)_increment == (dynamic)value) { return; } _increment = value; - IncrementChanged?.Invoke (this, new (value)); + IncrementChanged?.Invoke (this, new (value!)); } } diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 7256b5bfd..7dc85171f 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -69,74 +69,19 @@ public partial class Toplevel : View #region Subviews // TODO: Deprecate - Any view can host a menubar in v2 - /// Gets or sets the menu for this Toplevel. - public MenuBar? MenuBar { get; set; } + /// Gets the latest added into this Toplevel. + public MenuBar? MenuBar => (MenuBar?)Subviews?.LastOrDefault (s => s is MenuBar); // TODO: Deprecate - Any view can host a statusbar in v2 - /// Gets or sets the status bar for this Toplevel. - public StatusBar? StatusBar { get; set; } + /// Gets the latest added into this Toplevel. + public StatusBar? StatusBar => (StatusBar?)Subviews?.LastOrDefault (s => s is StatusBar); /// public override View Add (View view) { - AddMenuStatusBar (view); - return base.Add (view); } - /// - public override View Remove (View view) - { - if (this is Toplevel { MenuBar: { } }) - { - RemoveMenuStatusBar (view); - } - - return base.Remove (view); - } - - /// - public override void RemoveAll () - { - if (this == Application.Top) - { - MenuBar?.Dispose (); - MenuBar = null; - StatusBar?.Dispose (); - StatusBar = null; - } - - base.RemoveAll (); - } - - internal void AddMenuStatusBar (View view) - { - if (view is MenuBar) - { - MenuBar = view as MenuBar; - } - - if (view is StatusBar) - { - StatusBar = view as StatusBar; - } - } - - internal void RemoveMenuStatusBar (View view) - { - if (view is MenuBar) - { - MenuBar?.Dispose (); - MenuBar = null; - } - - if (view is StatusBar) - { - StatusBar?.Dispose (); - StatusBar = null; - } - } - // TODO: Overlapped - Rename to AllSubviewsClosed - Move to View? /// /// Invoked when the last child of the Toplevel is closed from by diff --git a/UICatalog/Scenarios/ListViewWithSelection.cs b/UICatalog/Scenarios/ListViewWithSelection.cs index f9bb7ea03..e22b2e2ec 100644 --- a/UICatalog/Scenarios/ListViewWithSelection.cs +++ b/UICatalog/Scenarios/ListViewWithSelection.cs @@ -202,9 +202,11 @@ public class ListViewWithSelection : Scenario return false; } - +#pragma warning disable CS0067 /// public event NotifyCollectionChangedEventHandler CollectionChanged; +#pragma warning restore CS0067 + public int Count => Scenarios?.Count ?? 0; public int Length { get; private set; } public bool SuspendCollectionChangedEvent { get => throw new System.NotImplementedException (); set => throw new System.NotImplementedException (); } diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 60353f59f..14b06f6e5 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -408,7 +408,7 @@ public class UICatalogApp _themeMenuItems = CreateThemeMenuItems (); _themeMenuBarItem = new ("_Themes", _themeMenuItems); - MenuBar = new () + MenuBar menuBar = new () { Menus = [ @@ -462,13 +462,15 @@ public class UICatalogApp ) ] }; + Add (menuBar); - StatusBar = new () + StatusBar statusBar = new () { Visible = ShowStatusBar, AlignmentModes = AlignmentModes.IgnoreFirstOrLast, CanFocus = false }; + Add (statusBar); if (StatusBar is { }) { @@ -484,7 +486,11 @@ public class UICatalogApp Title = "Show/Hide Status Bar", CanFocus = false, }; - statusBarShortcut.Accept += (sender, args) => { StatusBar.Visible = !StatusBar.Visible; }; + statusBarShortcut.Accept += (sender, args) => + { + StatusBar.Visible = !StatusBar.Visible; + args.Handled = true; + }; ShForce16Colors = new () { @@ -637,7 +643,7 @@ public class UICatalogApp Add (CategoryList); Add (ScenarioList); - Add (MenuBar); + Add (MenuBar!); if (StatusBar is { }) { diff --git a/UnitTests/Configuration/ConfigurationMangerTests.cs b/UnitTests/Configuration/ConfigurationMangerTests.cs index 44b681d28..fbca323b5 100644 --- a/UnitTests/Configuration/ConfigurationMangerTests.cs +++ b/UnitTests/Configuration/ConfigurationMangerTests.cs @@ -389,6 +389,7 @@ public class ConfigurationManagerTests ) ); +#pragma warning disable xUnit2029 Assert.Empty ( Settings.Where ( cp => cp.Value.PropertyInfo!.GetCustomAttribute ( @@ -397,6 +398,7 @@ public class ConfigurationManagerTests == null ) ); +#pragma warning restore xUnit2029 // Application is a static class PropertyInfo pi = typeof (Application).GetProperty ("QuitKey"); diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index 31d2f59f0..109065302 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -1,4 +1,5 @@ using System.Text; +using UICatalog; using Xunit.Abstractions; // Alias Console to MockConsole so we don't accidentally use Console @@ -7,37 +8,8 @@ namespace Terminal.Gui.TextTests; public class TextFormatterTests { - private readonly ITestOutputHelper _output; public TextFormatterTests (ITestOutputHelper output) { _output = output; } - - public static IEnumerable CMGlyphs => - new List { new object [] { $"{CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}", 16, 15 } }; - - public static IEnumerable FormatEnvironmentNewLine => - new List - { - new object [] - { - $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}", - 60, - new [] { "Line1", "Line2", "Line3" } - } - }; - - public static IEnumerable SplitEnvironmentNewLine => - new List - { - new object [] - { - $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界", - new [] { "First Line 界", "Second Line 界", "Third Line 界" } - }, - new object [] - { - $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界{Environment.NewLine}", - new [] { "First Line 界", "Second Line 界", "Third Line 界", "" } - } - }; + private readonly ITestOutputHelper _output; [Theory] [InlineData ("")] @@ -262,2760 +234,8 @@ public class TextFormatterTests ); } - [Theory] - [InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misęrables")] - [InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")] - [InlineData ( - 4, - 4, - TextDirection.TopBottom_LeftRight, - @" -LMre -eias -ssb - ęl " - )] - public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected) - { - var driver = new FakeDriver (); - driver.Init (); - - var text = "Les Mise\u0328\u0301rables"; - - var tf = new TextFormatter (); - tf.Direction = textDirection; - tf.Text = text; - - Assert.True (tf.WordWrap); - - tf.ConstrainToSize = new (width, height); - - tf.Draw ( - new (0, 0, width, height), - new (ColorName.White, ColorName.Black), - new (ColorName.Blue, ColorName.Black), - default (Rectangle), - driver - ); - TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); - - driver.End (); - } - - [Fact] - [SetupFakeDriver] - public void FillRemaining_True_False () - { - ((FakeDriver)Application.Driver!).SetBufferSize (22, 5); - - Attribute [] attrs = - { - Attribute.Default, new (ColorName.Green, ColorName.BrightMagenta), - new (ColorName.Blue, ColorName.Cyan) - }; - var tf = new TextFormatter { ConstrainToSize = new (14, 3), Text = "Test\nTest long\nTest long long\n", MultiLine = true }; - - tf.Draw ( - new (1, 1, 19, 3), - attrs [1], - attrs [2]); - - Assert.False (tf.FillRemaining); - - TestHelpers.AssertDriverContentsWithFrameAre ( - @" - Test - Test long - Test long long", - _output); - - TestHelpers.AssertDriverAttributesAre ( - @" -000000000000000000000 -011110000000000000000 -011111111100000000000 -011111111111111000000 -000000000000000000000", - null, - attrs); - - tf.FillRemaining = true; - - tf.Draw ( - new (1, 1, 19, 3), - attrs [1], - attrs [2]); - - TestHelpers.AssertDriverAttributesAre ( - @" -000000000000000000000 -011111111111111111110 -011111111111111111110 -011111111111111111110 -000000000000000000000", - null, - attrs); - } - - [Theory] - [InlineData ("_k Before", true, 0, (KeyCode)'K')] // lower case should return uppercase Hotkey - [InlineData ("a_k Second", true, 1, (KeyCode)'K')] - [InlineData ("Last _k", true, 5, (KeyCode)'K')] - [InlineData ("After k_", false, -1, KeyCode.Null)] - [InlineData ("Multiple _k and _R", true, 9, (KeyCode)'K')] - [InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к')] // Lower case Cryllic K (к) - [InlineData ("_k Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results - [InlineData ("a_k Second", true, 1, (KeyCode)'K', true)] - [InlineData ("Last _k", true, 5, (KeyCode)'K', true)] - [InlineData ("After k_", false, -1, KeyCode.Null, true)] - [InlineData ("Multiple _k and _r", true, 9, (KeyCode)'K', true)] - [InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к', true)] // Cryllic K (К) - public void FindHotKey_AlphaLowerCase_Succeeds ( - string text, - bool expectedResult, - int expectedHotPos, - KeyCode expectedKey, - bool supportFirstUpperCase = false - ) - { - var hotKeySpecifier = (Rune)'_'; - - bool result = TextFormatter.FindHotKey ( - text, - hotKeySpecifier, - out int hotPos, - out Key hotKey, - supportFirstUpperCase - ); - - if (expectedResult) - { - Assert.True (result); - } - else - { - Assert.False (result); - } - - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal (expectedKey, hotKey); - } - - [Theory] - [InlineData ("_K Before", true, 0, (KeyCode)'K')] - [InlineData ("a_K Second", true, 1, (KeyCode)'K')] - [InlineData ("Last _K", true, 5, (KeyCode)'K')] - [InlineData ("After K_", false, -1, KeyCode.Null)] - [InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K')] - [InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К) - [InlineData ("_K Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results - [InlineData ("a_K Second", true, 1, (KeyCode)'K', true)] - [InlineData ("Last _K", true, 5, (KeyCode)'K', true)] - [InlineData ("After K_", false, -1, KeyCode.Null, true)] - [InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K', true)] - [InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К', true)] // Cryllic K (К) - public void FindHotKey_AlphaUpperCase_Succeeds ( - string text, - bool expectedResult, - int expectedHotPos, - KeyCode expectedKey, - bool supportFirstUpperCase = false - ) - { - var hotKeySpecifier = (Rune)'_'; - - bool result = TextFormatter.FindHotKey ( - text, - hotKeySpecifier, - out int hotPos, - out Key hotKey, - supportFirstUpperCase - ); - - if (expectedResult) - { - Assert.True (result); - } - else - { - Assert.False (result); - } - - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal (expectedKey, hotKey); - } - - [Theory] - [InlineData (null)] - [InlineData ("")] - [InlineData ("no hotkey")] - [InlineData ("No hotkey, Upper Case")] - [InlineData ("Non-english: Сохранить")] - public void FindHotKey_Invalid_ReturnsFalse (string text) - { - var hotKeySpecifier = (Rune)'_'; - var supportFirstUpperCase = false; - var hotPos = 0; - Key hotKey = KeyCode.Null; - var result = false; - - result = TextFormatter.FindHotKey ( - text, - hotKeySpecifier, - out hotPos, - out hotKey, - supportFirstUpperCase - ); - Assert.False (result); - Assert.Equal (-1, hotPos); - Assert.Equal (KeyCode.Null, hotKey); - } - - [Theory] - [InlineData ("\"k before")] - [InlineData ("ak second")] - [InlineData ("last k")] - [InlineData ("multiple k and r")] - [InlineData ("12345")] - [InlineData ("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")] // punctuation - [InlineData (" ~  s  gui.cs   master ↑10")] // ~IsLetterOrDigit + Unicode - [InlineData ("non-english: кдать")] // Lower case Cryllic K (к) - public void FindHotKey_Legacy_FirstUpperCase_NotFound_Returns_False (string text) - { - var supportFirstUpperCase = true; - - var hotKeySpecifier = (Rune)0; - - bool result = TextFormatter.FindHotKey ( - text, - hotKeySpecifier, - out int hotPos, - out Key hotKey, - supportFirstUpperCase - ); - Assert.False (result); - Assert.Equal (-1, hotPos); - Assert.Equal (KeyCode.Null, hotKey); - } - - [Theory] - [InlineData ("K Before", true, 0, (KeyCode)'K')] - [InlineData ("aK Second", true, 1, (KeyCode)'K')] - [InlineData ("last K", true, 5, (KeyCode)'K')] - [InlineData ("multiple K and R", true, 9, (KeyCode)'K')] - [InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К) - public void FindHotKey_Legacy_FirstUpperCase_Succeeds ( - string text, - bool expectedResult, - int expectedHotPos, - KeyCode expectedKey - ) - { - var supportFirstUpperCase = true; - - var hotKeySpecifier = (Rune)0; - - bool result = TextFormatter.FindHotKey ( - text, - hotKeySpecifier, - out int hotPos, - out Key hotKey, - supportFirstUpperCase - ); - - if (expectedResult) - { - Assert.True (result); - } - else - { - Assert.False (result); - } - - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal (expectedKey, hotKey); - } - - [Theory] - [InlineData ("_1 Before", true, 0, (KeyCode)'1')] // Digits - [InlineData ("a_1 Second", true, 1, (KeyCode)'1')] - [InlineData ("Last _1", true, 5, (KeyCode)'1')] - [InlineData ("After 1_", false, -1, KeyCode.Null)] - [InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1')] - [InlineData ("_1 Before", true, 0, (KeyCode)'1', true)] // Turn on FirstUpperCase and verify same results - [InlineData ("a_1 Second", true, 1, (KeyCode)'1', true)] - [InlineData ("Last _1", true, 5, (KeyCode)'1', true)] - [InlineData ("After 1_", false, -1, KeyCode.Null, true)] - [InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1', true)] - public void FindHotKey_Numeric_Succeeds ( - string text, - bool expectedResult, - int expectedHotPos, - KeyCode expectedKey, - bool supportFirstUpperCase = false - ) - { - var hotKeySpecifier = (Rune)'_'; - - bool result = TextFormatter.FindHotKey ( - text, - hotKeySpecifier, - out int hotPos, - out Key hotKey, - supportFirstUpperCase - ); - - if (expectedResult) - { - Assert.True (result); - } - else - { - Assert.False (result); - } - - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal (expectedKey, hotKey); - } - - [Theory] - [InlineData ("_\"k before", true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char - [InlineData ("\"_k before", true, KeyCode.K)] - [InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')] - [InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')] - [InlineData ( - "`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", - true, - (KeyCode)'=' - )] // BUGBUG: Not sure why this fails. Ignore the first and consider the second - [InlineData ("_ ~  s  gui.cs   master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode - [InlineData (" ~  s  gui.cs  _ master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode - [InlineData ("non-english: _кдать", true, (KeyCode)'к')] // Lower case Cryllic K (к) - public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected) - { - var hotKeySpecifier = (Rune)'_'; - - bool result = TextFormatter.FindHotKey (text, hotKeySpecifier, out int _, out Key hotKey); - Assert.Equal (found, result); - Assert.Equal (expected, hotKey); - } - - [Fact] - public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_End_Spaces_As_True () - { - Exception exception = Record.Exception ( - () => - TextFormatter.Format ( - "Some text", - 4, - Alignment.Start, - false, - true - ) - ); - Assert.Null (exception); - } - - [Theory] - [InlineData ( - "Hello world, how are you today? Pretty neat!", - 44, - 80, - "Hello world, how are you today? Pretty neat!" - )] - public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Horizontal ( - string text, - int runeCount, - int maxWidth, - string justifiedText - ) - { - Assert.Equal (runeCount, text.GetRuneCount ()); - - var fmtText = string.Empty; - - for (int i = text.GetRuneCount (); i < maxWidth; i++) - { - fmtText = TextFormatter.Format (text, i, Alignment.Fill, true) [0]; - Assert.Equal (i, fmtText.GetRuneCount ()); - char c = fmtText [^1]; - Assert.True (text.EndsWith (c)); - } - - Assert.Equal (justifiedText, fmtText); - } - - [Theory] - [InlineData ( - "Hello world, how are you today? Pretty neat!", - 44, - 80, - "Hello world, how are you today? Pretty neat!" - )] - public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Vertical ( - string text, - int runeCount, - int maxWidth, - string justifiedText - ) - { - Assert.Equal (runeCount, text.GetRuneCount ()); - - var fmtText = string.Empty; - - for (int i = text.GetRuneCount (); i < maxWidth; i++) - { - fmtText = TextFormatter.Format ( - text, - i, - Alignment.Fill, - false, - true, - 0, - TextDirection.TopBottom_LeftRight - ) [0]; - Assert.Equal (i, fmtText.GetRuneCount ()); - char c = fmtText [^1]; - Assert.True (text.EndsWith (c)); - } - - Assert.Equal (justifiedText, fmtText); - } - - [Theory] - [InlineData ("Truncate", 3, "Tru")] - [InlineData ("デモエムポンズ", 3, "デ")] - public void Format_Truncate_Simple_And_Wide_Runes (string text, int width, string expected) - { - List list = TextFormatter.Format (text, width, false, false); - Assert.Equal (expected, list [^1]); - } - - [Theory] - [MemberData (nameof (FormatEnvironmentNewLine))] - public void Format_With_PreserveTrailingSpaces_And_Without_PreserveTrailingSpaces ( - string text, - int width, - IEnumerable expected - ) - { - var preserveTrailingSpaces = false; - List formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces); - Assert.Equal (expected, formated); - - preserveTrailingSpaces = true; - formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces); - Assert.Equal (expected, formated); - } - - [Theory] - [InlineData ( - " A sentence has words. \n This is the second Line - 2. ", - 4, - -50, - Alignment.Start, - true, - false, - new [] { " A", "sent", "ence", "has", "word", "s. ", " Thi", "s is", "the", "seco", "nd", "Line", "- 2." }, - " Asentencehaswords. This isthesecondLine- 2." - )] - [InlineData ( - " A sentence has words. \n This is the second Line - 2. ", - 4, - -50, - Alignment.Start, - true, - true, - new [] - { - " A ", - "sent", - "ence", - " ", - "has ", - "word", - "s. ", - " ", - "This", - " is ", - "the ", - "seco", - "nd ", - "Line", - " - ", - "2. " - }, - " A sentence has words. This is the second Line - 2. " - )] - public void Format_WordWrap_PreserveTrailingSpaces ( - string text, - int maxWidth, - int widthOffset, - Alignment alignment, - bool wrap, - bool preserveTrailingSpaces, - IEnumerable resultLines, - string expectedWrappedText - ) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - List list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces); - Assert.Equal (list.Count, resultLines.Count ()); - Assert.Equal (resultLines, list); - var wrappedText = string.Empty; - - foreach (string txt in list) - { - wrappedText += txt; - } - - Assert.Equal (expectedWrappedText, wrappedText); - } - - [Theory] - [InlineData ("Hello World", 11)] - [InlineData ("こんにちは世界", 14)] - public void GetColumns_Simple_And_Wide_Runes (string text, int width) { Assert.Equal (width, text.GetColumns ()); } - - [Theory] - [InlineData ("Hello World", 6, 6)] - [InlineData ("こんにちは 世界", 6, 3)] - [MemberData (nameof (CMGlyphs))] - public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength) - { - List runes = text.ToRuneList (); - Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns)); - } - - [Theory] - [InlineData ("test", 3, 3)] - [InlineData ("test", 4, 4)] - [InlineData ("test", 10, 4)] - public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength) - { - List runes = text.ToRuneList (); - - Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns)); - } - - [Theory] - [InlineData ("Hello World", 6, 6)] - [InlineData ("こんにちは 世界", 6, 3)] - public void GetLengthThatFits_Simple_And_Wide_Runes (string text, int columns, int expectedLength) - { - Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns)); - } - - [Theory] - [InlineData ("test", 3, 3)] - [InlineData ("test", 4, 4)] - [InlineData ("test", 10, 4)] - [InlineData ("test", 1, 1)] - [InlineData ("test", 0, 0)] - [InlineData ("test", -1, 0)] - [InlineData (null, -1, 0)] - [InlineData ("", -1, 0)] - public void GetLengthThatFits_String (string text, int columns, int expectedLength) - { - Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns)); - } - - [Fact] - public void GetLengthThatFits_With_Combining_Runes () - { - var text = "Les Mise\u0328\u0301rables"; - Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14)); - } - - [Fact] - public void GetMaxColsForWidth_With_Combining_Runes () - { - List text = new () { "Les Mis", "e\u0328\u0301", "rables" }; - Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1)); - } - - [Theory] - [InlineData (new [] { "0123456789" }, 1)] - [InlineData (new [] { "Hello World" }, 1)] - [InlineData (new [] { "Hello", "World" }, 2)] - [InlineData (new [] { "こんにちは", "世界" }, 4)] - public void GetColumnsRequiredForVerticalText_List_GetsWidth (IEnumerable text, int expectedWidth) - { - Assert.Equal (expectedWidth, TextFormatter.GetColumnsRequiredForVerticalText (text.ToList ())); - } - - [Theory] - [InlineData (new [] { "Hello World" }, 1, 0, 1, 1)] - [InlineData (new [] { "Hello", "World" }, 2, 1, 1, 1)] - [InlineData (new [] { "こんにちは", "世界" }, 4, 1, 1, 2)] - public void GetColumnsRequiredForVerticalText_List_Simple_And_Wide_Runes ( - IEnumerable text, - int expectedWidth, - int index, - int length, - int expectedIndexWidth - ) - { - Assert.Equal (expectedWidth, TextFormatter.GetColumnsRequiredForVerticalText (text.ToList ())); - Assert.Equal (expectedIndexWidth, TextFormatter.GetColumnsRequiredForVerticalText (text.ToList (), index, length)); - } - - [Fact] - public void GetColumnsRequiredForVerticalText_List_With_Combining_Runes () - { - List text = new () { "Les Mis", "e\u0328\u0301", "rables" }; - Assert.Equal (1, TextFormatter.GetColumnsRequiredForVerticalText (text, 1, 1)); - } - - //[Fact] - //public void GetWidestLineLength_With_Combining_Runes () - //{ - // var text = "Les Mise\u0328\u0301rables"; - // Assert.Equal (1, TextFormatter.GetWidestLineLength (text, 1, 1)); - //} - - [Fact] - public void Internal_Tests () - { - var tf = new TextFormatter (); - Assert.Equal (KeyCode.Null, tf.HotKey); - tf.HotKey = KeyCode.CtrlMask | KeyCode.Q; - Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, tf.HotKey); - } - - [Theory] - [InlineData ("")] - [InlineData (null)] - [InlineData ("test")] - public void Justify_Invalid (string text) - { - Assert.Equal (text, TextFormatter.Justify (text, 0)); - Assert.Equal (text, TextFormatter.Justify (text, 0)); - Assert.Throws (() => TextFormatter.Justify (text, -1)); - } - - [Theory] - - // Even # of spaces - // 0123456789 - [InlineData ("012 456 89", "012 456 89", 10, 0, "+", true)] - [InlineData ("012 456 89", "012++456+89", 11, 1)] - [InlineData ("012 456 89", "012 456 89", 12, 2, "++", true)] - [InlineData ("012 456 89", "012+++456++89", 13, 3)] - [InlineData ("012 456 89", "012 456 89", 14, 4, "+++", true)] - [InlineData ("012 456 89", "012++++456+++89", 15, 5)] - [InlineData ("012 456 89", "012 456 89", 16, 6, "++++", true)] - [InlineData ("012 456 89", "012 456 89", 30, 20, "+++++++++++", true)] - [InlineData ("012 456 89", "012+++++++++++++456++++++++++++89", 33, 23)] - - // Odd # of spaces - // 01234567890123 - [InlineData ("012 456 89 end", "012 456 89 end", 14, 0, "+", true)] - [InlineData ("012 456 89 end", "012++456+89+end", 15, 1)] - [InlineData ("012 456 89 end", "012++456++89+end", 16, 2)] - [InlineData ("012 456 89 end", "012 456 89 end", 17, 3, "++", true)] - [InlineData ("012 456 89 end", "012+++456++89++end", 18, 4)] - [InlineData ("012 456 89 end", "012+++456+++89++end", 19, 5)] - [InlineData ("012 456 89 end", "012 456 89 end", 20, 6, "+++", true)] - [InlineData ("012 456 89 end", "012++++++++456++++++++89+++++++end", 34, 20)] - [InlineData ("012 456 89 end", "012+++++++++456+++++++++89++++++++end", 37, 23)] - - // Unicode - // Even # of chars - // 0123456789 - [InlineData ("пÑРвРÑ", "пÑРвРÑ", 10, 0, "+", true)] - [InlineData ("пÑРвРÑ", "пÑÐ++вÐ+Ñ", 11, 1)] - [InlineData ("пÑРвРÑ", "пÑРвРÑ", 12, 2, "++", true)] - [InlineData ("пÑРвРÑ", "пÑÐ+++вÐ++Ñ", 13, 3)] - [InlineData ("пÑРвРÑ", "пÑРвРÑ", 14, 4, "+++", true)] - [InlineData ("пÑРвРÑ", "пÑÐ++++вÐ+++Ñ", 15, 5)] - [InlineData ("пÑРвРÑ", "пÑРвРÑ", 16, 6, "++++", true)] - [InlineData ("пÑРвРÑ", "пÑРвРÑ", 30, 20, "+++++++++++", true)] - [InlineData ("пÑРвРÑ", "пÑÐ+++++++++++++вÐ++++++++++++Ñ", 33, 23)] - - // Unicode - // Odd # of chars - // 0123456789 - [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 10, 0, "+", true)] - [InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ+вÐ+Ñ", 11, 1)] - [InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ++вÐ+Ñ", 12, 2)] - [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 13, 3, "++", true)] - [InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ++вÐ++Ñ", 14, 4)] - [InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ+++вÐ++Ñ", 15, 5)] - [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 16, 6, "+++", true)] - [InlineData ("Ð ÑРвРÑ", "Ð++++++++ÑÐ++++++++вÐ+++++++Ñ", 30, 20)] - [InlineData ("Ð ÑРвРÑ", "Ð+++++++++ÑÐ+++++++++вÐ++++++++Ñ", 33, 23)] - public void Justify_Sentence ( - string text, - string justifiedText, - int forceToWidth, - int widthOffset, - string replaceWith = null, - bool replace = false - ) - { - var fillChar = '+'; - - Assert.Equal (forceToWidth, text.GetRuneCount () + widthOffset); - - if (replace) - { - justifiedText = text.Replace (" ", replaceWith); - } - - Assert.Equal (justifiedText, TextFormatter.Justify (text, forceToWidth, fillChar)); - Assert.True (Math.Abs (forceToWidth - justifiedText.GetRuneCount ()) < text.Count (s => s == ' ')); - Assert.True (Math.Abs (forceToWidth - justifiedText.GetColumns ()) < text.Count (s => s == ' ')); - } - - [Theory] - [InlineData ("word")] // Even # of chars - [InlineData ("word.")] // Odd # of chars - [InlineData ("пÑивеÑ")] // Unicode (even #) - [InlineData ("пÑивеÑ.")] // Unicode (odd # of chars) - public void Justify_SingleWord (string text) - { - string justifiedText = text; - var fillChar = '+'; - - int width = text.GetRuneCount (); - Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); - width = text.GetRuneCount () + 1; - Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); - width = text.GetRuneCount () + 2; - Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); - width = text.GetRuneCount () + 10; - Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); - width = text.GetRuneCount () + 11; - Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); - } - - [Theory] - [InlineData ("Single Line 界", 14)] - [InlineData ("First Line 界\nSecond Line 界\nThird Line 界\n", 14)] - public void MaxWidthLine_With_And_Without_Newlines (string text, int expected) { Assert.Equal (expected, TextFormatter.GetWidestLineLength (text)); } - - [Theory] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 0, - 0, - false, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 0, - 1, - false, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 1, - 0, - false, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 0, - 0, - true, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 0, - 1, - true, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 1, - 0, - true, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 6, - 5, - false, - new [] { "First " } - )] - [InlineData ("1\n2\n3\n4\n5\n6", 6, 5, false, new [] { "1 2 3 " })] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 6, - 5, - true, - new [] { "First ", "Second", "Third ", "Forty ", "Fiftee" } - )] - [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, false, new [] { "第一" })] - [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, true, new [] { "第一", "第二", "第三", "四十", "第十" })] - public void MultiLine_WordWrap_False_Horizontal_Direction ( - string text, - int maxWidth, - int maxHeight, - bool multiLine, - IEnumerable resultLines - ) - { - var tf = new TextFormatter - { - Text = text, ConstrainToSize = new (maxWidth, maxHeight), WordWrap = false, MultiLine = multiLine - }; - - Assert.False (tf.WordWrap); - Assert.True (tf.MultiLine == multiLine); - Assert.Equal (TextDirection.LeftRight_TopBottom, tf.Direction); - - List splitLines = tf.GetLines (); - Assert.Equal (splitLines.Count, resultLines.Count ()); - Assert.Equal (splitLines, resultLines); - } - - [Theory] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 0, - 0, - false, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 0, - 1, - false, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 1, - 0, - false, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 0, - 0, - true, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 0, - 1, - true, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 1, - 0, - true, - new [] { "" } - )] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 6, - 5, - false, - new [] { "First" } - )] - [InlineData ("1\n2\n3\n4\n5\n6", 6, 5, false, new [] { "1 2 3" })] - [InlineData ( - "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", - 6, - 5, - true, - new [] { "First", "Secon", "Third", "Forty", "Fifte", "Seven" } - )] - [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, false, new [] { "第一行 第" })] - [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, true, new [] { "第一行", "第二行" })] - public void MultiLine_WordWrap_False_Vertical_Direction ( - string text, - int maxWidth, - int maxHeight, - bool multiLine, - IEnumerable resultLines - ) - { - var tf = new TextFormatter - { - Text = text, - ConstrainToSize = new (maxWidth, maxHeight), - WordWrap = false, - MultiLine = multiLine, - Direction = TextDirection.TopBottom_LeftRight - }; - - Assert.False (tf.WordWrap); - Assert.True (tf.MultiLine == multiLine); - Assert.Equal (TextDirection.TopBottom_LeftRight, tf.Direction); - - List splitLines = tf.GetLines (); - Assert.Equal (splitLines.Count, resultLines.Count ()); - Assert.Equal (splitLines, resultLines); - } - - [Fact] - public void NeedsFormat_Sets () - { - var testText = "test"; - var testBounds = new Rectangle (0, 0, 100, 1); - var tf = new TextFormatter (); - - tf.Text = "test"; - Assert.True (tf.NeedsFormat); // get_Lines causes a Format - Assert.NotEmpty (tf.GetLines ()); - Assert.False (tf.NeedsFormat); // get_Lines causes a Format - Assert.Equal (testText, tf.Text); - tf.Draw (testBounds, new (), new ()); - Assert.False (tf.NeedsFormat); - - tf.ConstrainToSize = new (1, 1); - Assert.True (tf.NeedsFormat); - Assert.NotEmpty (tf.GetLines ()); - Assert.False (tf.NeedsFormat); // get_Lines causes a Format - - tf.Alignment = Alignment.Center; - Assert.True (tf.NeedsFormat); - Assert.NotEmpty (tf.GetLines ()); - Assert.False (tf.NeedsFormat); // get_Lines causes a Format - } - - [Theory] - [InlineData ("", -1, Alignment.Start, false, 0)] - [InlineData (null, 0, Alignment.Start, false, 1)] - [InlineData (null, 0, Alignment.Start, true, 1)] - [InlineData ("", 0, Alignment.Start, false, 1)] - [InlineData ("", 0, Alignment.Start, true, 1)] - public void Reformat_Invalid (string text, int maxWidth, Alignment alignment, bool wrap, int linesCount) - { - if (maxWidth < 0) - { - Assert.Throws ( - () => - TextFormatter.Format (text, maxWidth, alignment, wrap) - ); - } - else - { - List list = TextFormatter.Format (text, maxWidth, alignment, wrap); - Assert.NotEmpty (list); - Assert.True (list.Count == linesCount); - Assert.Equal (string.Empty, list [0]); - } - } - - [Theory] - [InlineData ("A sentence has words.\nLine 2.", 0, -29, Alignment.Start, false, 1, true)] - [InlineData ("A sentence has words.\nLine 2.", 1, -28, Alignment.Start, false, 1, false)] - [InlineData ("A sentence has words.\nLine 2.", 5, -24, Alignment.Start, false, 1, false)] - [InlineData ("A sentence has words.\nLine 2.", 28, -1, Alignment.Start, false, 1, false)] - - // no clip - [InlineData ("A sentence has words.\nLine 2.", 29, 0, Alignment.Start, false, 1, false)] - [InlineData ("A sentence has words.\nLine 2.", 30, 1, Alignment.Start, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, Alignment.Start, false, 1, true)] - [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, Alignment.Start, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, Alignment.Start, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, Alignment.Start, false, 1, false, 1)] - [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, Alignment.Start, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, Alignment.Start, false, 1, false)] - public void Reformat_NoWordrap_NewLines_MultiLine_False ( - string text, - int maxWidth, - int widthOffset, - Alignment alignment, - bool wrap, - int linesCount, - bool stringEmpty, - int clipWidthOffset = 0 - ) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset; - List list = TextFormatter.Format (text, maxWidth, alignment, wrap); - Assert.NotEmpty (list); - Assert.True (list.Count == linesCount); - - if (stringEmpty) - { - Assert.Equal (string.Empty, list [0]); - } - else - { - Assert.NotEqual (string.Empty, list [0]); - } - - if (text.Contains ("\r\n") && maxWidth > 0) - { - Assert.Equal ( - StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]) - .Replace ("\r\n", " "), - list [0] - ); - } - else if (text.Contains ('\n') && maxWidth > 0) - { - Assert.Equal ( - StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]) - .Replace ("\n", " "), - list [0] - ); - } - else - { - Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]); - } - } - - [Theory] - [InlineData ("A sentence has words.\nLine 2.", 0, -29, Alignment.Start, false, 1, true, new [] { "" })] - [InlineData ( - "A sentence has words.\nLine 2.", - 1, - -28, - Alignment.Start, - false, - 2, - false, - new [] { "A", "L" } - )] - [InlineData ( - "A sentence has words.\nLine 2.", - 5, - -24, - Alignment.Start, - false, - 2, - false, - new [] { "A sen", "Line " } - )] - [InlineData ( - "A sentence has words.\nLine 2.", - 28, - -1, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - //// no clip - [InlineData ( - "A sentence has words.\nLine 2.", - 29, - 0, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - [InlineData ( - "A sentence has words.\nLine 2.", - 30, - 1, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, Alignment.Start, false, 1, true, new [] { "" })] - [InlineData ( - "A sentence has words.\r\nLine 2.", - 1, - -29, - Alignment.Start, - false, - 2, - false, - new [] { "A", "L" } - )] - [InlineData ( - "A sentence has words.\r\nLine 2.", - 5, - -25, - Alignment.Start, - false, - 2, - false, - new [] { "A sen", "Line " } - )] - [InlineData ( - "A sentence has words.\r\nLine 2.", - 29, - -1, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - [InlineData ( - "A sentence has words.\r\nLine 2.", - 30, - 0, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - [InlineData ( - "A sentence has words.\r\nLine 2.", - 31, - 1, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - public void Reformat_NoWordrap_NewLines_MultiLine_True ( - string text, - int maxWidth, - int widthOffset, - Alignment alignment, - bool wrap, - int linesCount, - bool stringEmpty, - IEnumerable resultLines - ) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - - List list = TextFormatter.Format ( - text, - maxWidth, - alignment, - wrap, - false, - 0, - TextDirection.LeftRight_TopBottom, - true - ); - Assert.NotEmpty (list); - Assert.True (list.Count == linesCount); - - if (stringEmpty) - { - Assert.Equal (string.Empty, list [0]); - } - else - { - Assert.NotEqual (string.Empty, list [0]); - } - - Assert.Equal (list, resultLines); - } - - [Theory] - [InlineData ("A sentence has words.\nLine 2.", 0, -29, Alignment.Start, false, 1, true, new [] { "" })] - [InlineData ( - "A sentence has words.\nLine 2.", - 1, - -28, - Alignment.Start, - false, - 2, - false, - new [] { "A", "L" } - )] - [InlineData ( - "A sentence has words.\nLine 2.", - 5, - -24, - Alignment.Start, - false, - 2, - false, - new [] { "A sen", "Line " } - )] - [InlineData ( - "A sentence has words.\nLine 2.", - 28, - -1, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - //// no clip - [InlineData ( - "A sentence has words.\nLine 2.", - 29, - 0, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - [InlineData ( - "A sentence has words.\nLine 2.", - 30, - 1, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, Alignment.Start, false, 1, true, new [] { "" })] - [InlineData ( - "A sentence has words.\r\nLine 2.", - 1, - -29, - Alignment.Start, - false, - 2, - false, - new [] { "A", "L" } - )] - [InlineData ( - "A sentence has words.\r\nLine 2.", - 5, - -25, - Alignment.Start, - false, - 2, - false, - new [] { "A sen", "Line " } - )] - [InlineData ( - "A sentence has words.\r\nLine 2.", - 29, - -1, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - [InlineData ( - "A sentence has words.\r\nLine 2.", - 30, - 0, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - [InlineData ( - "A sentence has words.\r\nLine 2.", - 31, - 1, - Alignment.Start, - false, - 2, - false, - new [] { "A sentence has words.", "Line 2." } - )] - public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical ( - string text, - int maxWidth, - int widthOffset, - Alignment alignment, - bool wrap, - int linesCount, - bool stringEmpty, - IEnumerable resultLines - ) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - - List list = TextFormatter.Format ( - text, - maxWidth, - alignment, - wrap, - false, - 0, - TextDirection.TopBottom_LeftRight, - true - ); - Assert.NotEmpty (list); - Assert.True (list.Count == linesCount); - - if (stringEmpty) - { - Assert.Equal (string.Empty, list [0]); - } - else - { - Assert.NotEqual (string.Empty, list [0]); - } - - Assert.Equal (list, resultLines); - } - - [Theory] - [InlineData ("", 0, 0, Alignment.Start, false, 1, true)] - [InlineData ("", 1, 1, Alignment.Start, false, 1, true)] - [InlineData ("A sentence has words.", 0, -21, Alignment.Start, false, 1, true)] - [InlineData ("A sentence has words.", 1, -20, Alignment.Start, false, 1, false)] - [InlineData ("A sentence has words.", 5, -16, Alignment.Start, false, 1, false)] - [InlineData ("A sentence has words.", 20, -1, Alignment.Start, false, 1, false)] - - // no clip - [InlineData ("A sentence has words.", 21, 0, Alignment.Start, false, 1, false)] - [InlineData ("A sentence has words.", 22, 1, Alignment.Start, false, 1, false)] - public void Reformat_NoWordrap_SingleLine ( - string text, - int maxWidth, - int widthOffset, - Alignment alignment, - bool wrap, - int linesCount, - bool stringEmpty - ) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - List list = TextFormatter.Format (text, maxWidth, alignment, wrap); - Assert.NotEmpty (list); - Assert.True (list.Count == linesCount); - - if (stringEmpty) - { - Assert.Equal (string.Empty, list [0]); - } - else - { - Assert.NotEqual (string.Empty, list [0]); - } - - Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]); - } - - [Theory] - - // Unicode - [InlineData ( - "\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", - 8, - -1, - Alignment.Start, - true, - false, - new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" } - )] - - // no clip - [InlineData ( - "\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", - 9, - 0, - Alignment.Start, - true, - false, - new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" } - )] - [InlineData ( - "\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", - 10, - 1, - Alignment.Start, - true, - false, - new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" } - )] - public void Reformat_Unicode_Wrap_Spaces_NewLines ( - string text, - int maxWidth, - int widthOffset, - Alignment alignment, - bool wrap, - bool preserveTrailingSpaces, - IEnumerable resultLines - ) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - List list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces); - Assert.Equal (list.Count, resultLines.Count ()); - Assert.Equal (resultLines, list); - } - - [Theory] - - // Unicode - // Even # of chars - // 0123456789 - [InlineData ("\u2660пÑРвРÑ", 10, -1, Alignment.Start, true, false, new [] { "\u2660пÑРвÐ", "Ñ" })] - - // no clip - [InlineData ("\u2660пÑРвРÑ", 11, 0, Alignment.Start, true, false, new [] { "\u2660пÑРвРÑ" })] - [InlineData ("\u2660пÑРвРÑ", 12, 1, Alignment.Start, true, false, new [] { "\u2660пÑРвРÑ" })] - - // Unicode - // Odd # of chars - // 0123456789 - [InlineData ("\u2660 ÑРвРÑ", 9, -1, Alignment.Start, true, false, new [] { "\u2660 ÑРвÐ", "Ñ" })] - - // no clip - [InlineData ("\u2660 ÑРвРÑ", 10, 0, Alignment.Start, true, false, new [] { "\u2660 ÑРвРÑ" })] - [InlineData ("\u2660 ÑРвРÑ", 11, 1, Alignment.Start, true, false, new [] { "\u2660 ÑРвРÑ" })] - public void Reformat_Unicode_Wrap_Spaces_No_NewLines ( - string text, - int maxWidth, - int widthOffset, - Alignment alignment, - bool wrap, - bool preserveTrailingSpaces, - IEnumerable resultLines - ) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - List list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces); - Assert.Equal (list.Count, resultLines.Count ()); - Assert.Equal (resultLines, list); - } - - [Theory] - - // Even # of spaces - // 0123456789 - [InlineData ("012 456 89", 0, -10, Alignment.Start, true, true, true, new [] { "" })] - [InlineData ( - "012 456 89", - 1, - -9, - Alignment.Start, - true, - true, - false, - new [] { "0", "1", "2", " ", "4", "5", "6", " ", "8", "9" }, - "01245689" - )] - [InlineData ("012 456 89", 5, -5, Alignment.Start, true, true, false, new [] { "012 ", "456 ", "89" })] - [InlineData ("012 456 89", 9, -1, Alignment.Start, true, true, false, new [] { "012 456 ", "89" })] - - // no clip - [InlineData ("012 456 89", 10, 0, Alignment.Start, true, true, false, new [] { "012 456 89" })] - [InlineData ("012 456 89", 11, 1, Alignment.Start, true, true, false, new [] { "012 456 89" })] - - // Odd # of spaces - // 01234567890123 - [InlineData ("012 456 89 end", 13, -1, Alignment.Start, true, true, false, new [] { "012 456 89 ", "end" })] - - // no clip - [InlineData ("012 456 89 end", 14, 0, Alignment.Start, true, true, false, new [] { "012 456 89 end" })] - [InlineData ("012 456 89 end", 15, 1, Alignment.Start, true, true, false, new [] { "012 456 89 end" })] - public void Reformat_Wrap_Spaces_No_NewLines ( - string text, - int maxWidth, - int widthOffset, - Alignment alignment, - bool wrap, - bool preserveTrailingSpaces, - bool stringEmpty, - IEnumerable resultLines, - string noSpaceText = "" - ) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - List list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces); - Assert.NotEmpty (list); - Assert.True (list.Count == resultLines.Count ()); - - if (stringEmpty) - { - Assert.Equal (string.Empty, list [0]); - } - else - { - Assert.NotEqual (string.Empty, list [0]); - } - - Assert.Equal (resultLines, list); - - if (maxWidth > 0) - { - // remove whitespace chars - if (maxWidth < 5) - { - expectedClippedWidth = text.GetRuneCount () - text.Sum (r => r == ' ' ? 1 : 0); - } - else - { - expectedClippedWidth = Math.Min ( - text.GetRuneCount (), - maxWidth - text.Sum (r => r == ' ' ? 1 : 0) - ); - } - - list = TextFormatter.Format (text, maxWidth, Alignment.Start, wrap); - - if (maxWidth == 1) - { - Assert.Equal (expectedClippedWidth, list.Count); - Assert.Equal (noSpaceText, string.Concat (list.ToArray ())); - } - - if (maxWidth > 1 && maxWidth < 10) - { - Assert.Equal ( - StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), - list [0] - ); - } - } - } - - [Theory] - [InlineData (null)] - [InlineData ("")] - [InlineData ("a")] - public void RemoveHotKeySpecifier_InValid_ReturnsOriginal (string text) - { - var hotKeySpecifier = (Rune)'_'; - - if (text == null) - { - Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier)); - Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier)); - Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier)); - } - else - { - Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier)); - Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier)); - Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier)); - } - } - - [Theory] - [InlineData ("all lower case", 0)] - [InlineData ("K Before", 0)] - [InlineData ("aK Second", 1)] - [InlineData ("Last K", 5)] - [InlineData ("fter K", 7)] - [InlineData ("Multiple K and R", 9)] - [InlineData ("Non-english: Кдать", 13)] - public void RemoveHotKeySpecifier_Valid_Legacy_ReturnsOriginal (string text, int hotPos) - { - var hotKeySpecifier = (Rune)'_'; - - Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier)); - } - - [Theory] - [InlineData ("_K Before", 0, "K Before")] - [InlineData ("a_K Second", 1, "aK Second")] - [InlineData ("Last _K", 5, "Last K")] - [InlineData ("After K_", 7, "After K")] - [InlineData ("Multiple _K and _R", 9, "Multiple K and _R")] - [InlineData ("Non-english: _Кдать", 13, "Non-english: Кдать")] - public void RemoveHotKeySpecifier_Valid_ReturnsStripped (string text, int hotPos, string expectedText) - { - var hotKeySpecifier = (Rune)'_'; - - Assert.Equal (expectedText, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier)); - } - - [Theory] - [InlineData ("test", 0, 't', "test")] - [InlineData ("test", 1, 'e', "test")] - [InlineData ("Ok", 0, 'O', "Ok")] - [InlineData ("[◦ Ok ◦]", 3, 'O', "[◦ Ok ◦]")] - [InlineData ("^k", 0, '^', "^k")] - public void ReplaceHotKeyWithTag (string text, int hotPos, uint tag, string expected) - { - var tf = new TextFormatter (); - List runes = text.ToRuneList (); - Rune rune; - - if (Rune.TryGetRuneAt (text, hotPos, out rune)) - { - Assert.Equal (rune, (Rune)tag); - } - - string result = TextFormatter.ReplaceHotKeyWithTag (text, hotPos); - Assert.Equal (result, expected); - Assert.Equal ((Rune)tag, result.ToRunes () [hotPos]); - Assert.Equal (text.GetRuneCount (), runes.Count); - Assert.Equal (text, StringExtensions.ToString (runes)); - } - - [Theory] - [MemberData (nameof (SplitEnvironmentNewLine))] - public void SplitNewLine_Ending__With_Or_Without_NewLine_Probably_CRLF ( - string text, - IEnumerable expected - ) - { - List splited = TextFormatter.SplitNewLine (text); - Assert.Equal (expected, splited); - } - - [Theory] - [InlineData ( - "First Line 界\nSecond Line 界\nThird Line 界\n", - new [] { "First Line 界", "Second Line 界", "Third Line 界", "" } - )] - public void SplitNewLine_Ending_With_NewLine_Only_LF (string text, IEnumerable expected) - { - List splited = TextFormatter.SplitNewLine (text); - Assert.Equal (expected, splited); - } - - [Theory] - [InlineData ( - "First Line 界\nSecond Line 界\nThird Line 界", - new [] { "First Line 界", "Second Line 界", "Third Line 界" } - )] - public void SplitNewLine_Ending_Without_NewLine_Only_LF (string text, IEnumerable expected) - { - List splited = TextFormatter.SplitNewLine (text); - Assert.Equal (expected, splited); - } - - [Theory] - [InlineData ("New Test 你", 10, 10, 20320, 20320, 9, "你")] - [InlineData ("New Test \U0001d539", 10, 11, 120121, 55349, 9, "𝔹")] - public void String_Array_Is_Not_Always_Equal_ToRunes_Array ( - string text, - int runesLength, - int stringLength, - int runeValue, - int stringValue, - int index, - string expected - ) - { - Rune [] usToRunes = text.ToRunes (); - Assert.Equal (runesLength, usToRunes.Length); - Assert.Equal (stringLength, text.Length); - Assert.Equal (runeValue, usToRunes [index].Value); - Assert.Equal (stringValue, text [index]); - Assert.Equal (expected, usToRunes [index].ToString ()); - - if (char.IsHighSurrogate (text [index])) - { - // Rune array length isn't equal to string array - Assert.Equal (expected, new (new [] { text [index], text [index + 1] })); - } - else - { - // Rune array length is equal to string array - Assert.Equal (expected, text [index].ToString ()); - } - } - - [Theory] - [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] - [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] - [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] - [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] - public void TabWith_PreserveTrailingSpaces_False ( - int width, - int height, - TextDirection textDirection, - int tabWidth, - string expected - ) - { - var driver = new FakeDriver (); - driver.Init (); - - var text = "This is a \tTab"; - var tf = new TextFormatter (); - tf.Direction = textDirection; - tf.TabWidth = tabWidth; - tf.Text = text; - tf.ConstrainToWidth = 20; - tf.ConstrainToHeight = 20; - - Assert.True (tf.WordWrap); - Assert.False (tf.PreserveTrailingSpaces); - - tf.Draw ( - new (0, 0, width, height), - new (ColorName.White, ColorName.Black), - new (ColorName.Blue, ColorName.Black), - default (Rectangle), - driver - ); - TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); - - driver.End (); - } - - [Theory] - [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] - [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] - [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] - [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] - public void TabWith_PreserveTrailingSpaces_True ( - int width, - int height, - TextDirection textDirection, - int tabWidth, - string expected - ) - { - var driver = new FakeDriver (); - driver.Init (); - - var text = "This is a \tTab"; - var tf = new TextFormatter (); - - tf.Direction = textDirection; - tf.TabWidth = tabWidth; - tf.PreserveTrailingSpaces = true; - tf.Text = text; - tf.ConstrainToWidth = 20; - tf.ConstrainToHeight = 20; - - Assert.True (tf.WordWrap); - - tf.Draw ( - new (0, 0, width, height), - new (ColorName.White, ColorName.Black), - new (ColorName.Blue, ColorName.Black), - default (Rectangle), - driver - ); - TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); - - driver.End (); - } - - [Theory] - [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] - [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] - [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] - [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] - public void TabWith_WordWrap_True ( - int width, - int height, - TextDirection textDirection, - int tabWidth, - string expected - ) - { - var driver = new FakeDriver (); - driver.Init (); - - var text = "This is a \tTab"; - var tf = new TextFormatter (); - - tf.Direction = textDirection; - tf.TabWidth = tabWidth; - tf.WordWrap = true; - tf.Text = text; - tf.ConstrainToWidth = 20; - tf.ConstrainToHeight = 20; - - Assert.False (tf.PreserveTrailingSpaces); - - tf.Draw ( - new (0, 0, width, height), - new (ColorName.White, ColorName.Black), - new (ColorName.Blue, ColorName.Black), - default (Rectangle), - driver - ); - TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); - - driver.End (); - } - - [Theory] - [InlineData ("123456789", 3, "123")] - [InlineData ("Hello World", 8, "Hello Wo")] - public void TestClipOrPad_LongWord (string text, int fillPad, string expectedText) - { - // word is long but we want it to fill # space only - Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad)); - } - - [Theory] - [InlineData ("fff", 6, "fff ")] - [InlineData ("Hello World", 16, "Hello World ")] - public void TestClipOrPad_ShortWord (string text, int fillPad, string expectedText) - { - // word is short but we want it to fill # so it should be padded - Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad)); - } - - [Theory] - [InlineData ("你", TextDirection.LeftRight_TopBottom, 2, 1)] - [InlineData ("你", TextDirection.TopBottom_LeftRight, 2, 1)] - [InlineData ("你你", TextDirection.LeftRight_TopBottom, 4, 1)] - [InlineData ("你你", TextDirection.TopBottom_LeftRight, 2, 2)] - public void Text_Set_SizeIsCorrect (string text, TextDirection textDirection, int expectedWidth, int expectedHeight) - { - var tf = new TextFormatter { Direction = textDirection, Text = text }; - tf.ConstrainToWidth = 10; - tf.ConstrainToHeight = 10; - - Assert.Equal (new (expectedWidth, expectedHeight), tf.FormatAndGetSize ()); - } - - [Fact] - public void WordWrap_BigWidth () - { - List wrappedLines; - - var text = "Constantinople"; - wrappedLines = TextFormatter.WordWrapText (text, 100); - Assert.True (wrappedLines.Count == 1); - Assert.Equal ("Constantinople", wrappedLines [0]); - } - - [Fact] - public void WordWrap_Invalid () - { - var text = string.Empty; - var width = 0; - - Assert.Empty (TextFormatter.WordWrapText (null, width)); - Assert.Empty (TextFormatter.WordWrapText (text, width)); - Assert.Throws (() => TextFormatter.WordWrapText (text, -1)); - } - - [Theory] - [InlineData ("A sentence has words.", 3, -18, new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] - [InlineData ( - "A sentence has words.", - 2, - -19, - new [] { "A", "se", "nt", "en", "ce", "ha", "s", "wo", "rd", "s." } - )] - [InlineData ( - "A sentence has words.", - 1, - -20, - new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." } - )] - public void WordWrap_Narrow_Default ( - string text, - int maxWidth, - int widthOffset, - IEnumerable resultLines - ) - { - // Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - [Theory] - [InlineData ("A sentence has words.", 21, 0, new [] { "A sentence has words." })] - [InlineData ("A sentence has words.", 20, -1, new [] { "A sentence has", "words." })] - [InlineData ("A sentence has words.", 15, -6, new [] { "A sentence has", "words." })] - [InlineData ("A sentence has words.", 14, -7, new [] { "A sentence has", "words." })] - [InlineData ("A sentence has words.", 13, -8, new [] { "A sentence", "has words." })] - - // Unicode - [InlineData ( - "A Unicode sentence (пÑивеÑ) has words.", - 42, - 0, - new [] { "A Unicode sentence (пÑивеÑ) has words." } - )] - [InlineData ( - "A Unicode sentence (пÑивеÑ) has words.", - 41, - -1, - new [] { "A Unicode sentence (пÑивеÑ) has", "words." } - )] - [InlineData ( - "A Unicode sentence (пÑивеÑ) has words.", - 36, - -6, - new [] { "A Unicode sentence (пÑивеÑ) has", "words." } - )] - [InlineData ( - "A Unicode sentence (пÑивеÑ) has words.", - 35, - -7, - new [] { "A Unicode sentence (пÑивеÑ) has", "words." } - )] - [InlineData ( - "A Unicode sentence (пÑивеÑ) has words.", - 34, - -8, - new [] { "A Unicode sentence (пÑивеÑ)", "has words." } - )] - [InlineData ( - "A Unicode sentence (пÑивеÑ) has words.", - 25, - -17, - new [] { "A Unicode sentence", "(пÑивеÑ) has words." } - )] - public void WordWrap_NoNewLines_Default ( - string text, - int maxWidth, - int widthOffset, - IEnumerable resultLines - ) - { - // Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - [Theory] - [InlineData ("これが最初の行です。 こんにちは世界。 これが2行目です。", 29, 0, new [] { "これが最初の行です。", "こんにちは世界。", "これが2行目です。" })] - public void WordWrap_PreserveTrailingSpaces_False_Unicode_Wide_Runes ( - string text, - int maxWidth, - int widthOffset, - IEnumerable resultLines - ) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - [Theory] - [InlineData ("文に は言葉 があり ます。", 14, 0, new [] { "文に は言葉", "があり ます。" })] - [InlineData ("文に は言葉 があり ます。", 3, -11, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })] - [InlineData ("文に は言葉 があり ます。", 2, -12, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })] - [InlineData ( - "文に は言葉 があり ます。", - 1, - -13, - new [] { " ", " ", " " } - )] // Just Spaces; should result in a single space for each line - public void WordWrap_PreserveTrailingSpaces_False_Wide_Runes ( - string text, - int maxWidth, - int widthOffset, - IEnumerable resultLines - ) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - [Theory] - [InlineData (null, 1, new string [] { })] // null input - [InlineData ("", 1, new string [] { })] // Empty input - [InlineData ("1 34", 1, new [] { "1", "3", "4" })] // Single Spaces - [InlineData ("1", 1, new [] { "1" })] // Short input - [InlineData ("12", 1, new [] { "1", "2" })] - [InlineData ("123", 1, new [] { "1", "2", "3" })] - [InlineData ("123456", 1, new [] { "1", "2", "3", "4", "5", "6" })] // No spaces - [InlineData (" ", 1, new [] { " " })] // Just Spaces; should result in a single space - [InlineData (" ", 1, new [] { " " })] - [InlineData (" ", 1, new [] { " ", " " })] - [InlineData (" ", 1, new [] { " ", " " })] - [InlineData ("12 456", 1, new [] { "1", "2", "4", "5", "6" })] // Single Spaces - [InlineData (" 2 456", 1, new [] { " ", "2", "4", "5", "6" })] // Leading spaces should be preserved. - [InlineData (" 2 456 8", 1, new [] { " ", "2", "4", "5", "6", "8" })] - [InlineData ( - "A sentence has words. ", - 1, - new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." } - )] // Complex example - [InlineData ("12 567", 1, new [] { "1", "2", " ", "5", "6", "7" })] // Double Spaces - [InlineData (" 3 567", 1, new [] { " ", "3", "5", "6", "7" })] // Double Leading spaces should be preserved. - [InlineData (" 3 678 1", 1, new [] { " ", "3", " ", "6", "7", "8", " ", "1" })] - [InlineData ("1 456", 1, new [] { "1", " ", "4", "5", "6" })] - [InlineData ( - "A sentence has words. ", - 1, - new [] - { - "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", "w", "o", "r", "d", "s", ".", " " - } - )] // Double space Complex example - public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_1 ( - string text, - int width, - IEnumerable resultLines - ) - { - List wrappedLines = TextFormatter.WordWrapText (text, width); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - - foreach (string line in wrappedLines) - { - breakLines += $"{line}{Environment.NewLine}"; - } - - var expected = string.Empty; - - foreach (string line in resultLines) - { - expected += $"{line}{Environment.NewLine}"; - } - - Assert.Equal (expected, breakLines); - } - - [Theory] - [InlineData (null, 3, new string [] { })] // null input - [InlineData ("", 3, new string [] { })] // Empty input - [InlineData ("1", 3, new [] { "1" })] // Short input - [InlineData ("12", 3, new [] { "12" })] - [InlineData ("123", 3, new [] { "123" })] - [InlineData ("123456", 3, new [] { "123", "456" })] // No spaces - [InlineData ("1234567", 3, new [] { "123", "456", "7" })] // No spaces - [InlineData (" ", 3, new [] { " " })] // Just Spaces; should result in a single space - [InlineData (" ", 3, new [] { " " })] - [InlineData (" ", 3, new [] { " " })] - [InlineData (" ", 3, new [] { " " })] - [InlineData ("12 456", 3, new [] { "12", "456" })] // Single Spaces - [InlineData (" 2 456", 3, new [] { " 2", "456" })] // Leading spaces should be preserved. - [InlineData (" 2 456 8", 3, new [] { " 2", "456", "8" })] - [InlineData ( - "A sentence has words. ", - 3, - new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." } - )] // Complex example - [InlineData ("12 567", 3, new [] { "12 ", "567" })] // Double Spaces - [InlineData (" 3 567", 3, new [] { " 3", "567" })] // Double Leading spaces should be preserved. - [InlineData (" 3 678 1", 3, new [] { " 3", " 67", "8 ", "1" })] - [InlineData ("1 456", 3, new [] { "1 ", "456" })] - [InlineData ( - "A sentence has words. ", - 3, - new [] { "A ", "sen", "ten", "ce ", " ", "has", "wor", "ds.", " " } - )] // Double space Complex example - public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_3 ( - string text, - int width, - IEnumerable resultLines - ) - { - List wrappedLines = TextFormatter.WordWrapText (text, width); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - - foreach (string line in wrappedLines) - { - breakLines += $"{line}{Environment.NewLine}"; - } - - var expected = string.Empty; - - foreach (string line in resultLines) - { - expected += $"{line}{Environment.NewLine}"; - } - - Assert.Equal (expected, breakLines); - } - - [Theory] - [InlineData (null, 50, new string [] { })] // null input - [InlineData ("", 50, new string [] { })] // Empty input - [InlineData ("1", 50, new [] { "1" })] // Short input - [InlineData ("12", 50, new [] { "12" })] - [InlineData ("123", 50, new [] { "123" })] - [InlineData ("123456", 50, new [] { "123456" })] // No spaces - [InlineData ("1234567", 50, new [] { "1234567" })] // No spaces - [InlineData (" ", 50, new [] { " " })] // Just Spaces; should result in a single space - [InlineData (" ", 50, new [] { " " })] - [InlineData (" ", 50, new [] { " " })] - [InlineData ("12 456", 50, new [] { "12 456" })] // Single Spaces - [InlineData (" 2 456", 50, new [] { " 2 456" })] // Leading spaces should be preserved. - [InlineData (" 2 456 8", 50, new [] { " 2 456 8" })] - [InlineData ("A sentence has words. ", 50, new [] { "A sentence has words. " })] // Complex example - [InlineData ("12 567", 50, new [] { "12 567" })] // Double Spaces - [InlineData (" 3 567", 50, new [] { " 3 567" })] // Double Leading spaces should be preserved. - [InlineData (" 3 678 1", 50, new [] { " 3 678 1" })] - [InlineData ("1 456", 50, new [] { "1 456" })] - [InlineData ( - "A sentence has words. ", - 50, - new [] { "A sentence has words. " } - )] // Double space Complex example - public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_50 ( - string text, - int width, - IEnumerable resultLines - ) - { - List wrappedLines = TextFormatter.WordWrapText (text, width); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - - foreach (string line in wrappedLines) - { - breakLines += $"{line}{Environment.NewLine}"; - } - - var expected = string.Empty; - - foreach (string line in resultLines) - { - expected += $"{line}{Environment.NewLine}"; - } - - Assert.Equal (expected, breakLines); - } - - [Theory] - [InlineData ("A sentence has words.", 14, -7, new [] { "A sentence ", "has words." })] - [InlineData ("A sentence has words.", 8, -13, new [] { "A ", "sentence", " has ", "words." })] - [InlineData ("A sentence has words.", 6, -15, new [] { "A ", "senten", "ce ", "has ", "words." })] - [InlineData ("A sentence has words.", 3, -18, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds." })] - [InlineData ( - "A sentence has words.", - 2, - -19, - new [] { "A ", "se", "nt", "en", "ce", " ", "ha", "s ", "wo", "rd", "s." } - )] - [InlineData ( - "A sentence has words.", - 1, - -20, - new [] - { - "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." - } - )] - public void WordWrap_PreserveTrailingSpaces_True ( - string text, - int maxWidth, - int widthOffset, - IEnumerable resultLines - ) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - [Theory] - [InlineData ("文に は言葉 があり ます。", 14, 0, new [] { "文に は言葉 ", "があり ます。" })] - [InlineData ("文に は言葉 があり ます。", 3, -11, new [] { "文", "に ", "は", "言", "葉 ", "が", "あ", "り ", "ま", "す", "。" })] - [InlineData ( - "文に は言葉 があり ます。", - 2, - -12, - new [] { "文", "に", " ", "は", "言", "葉", " ", "が", "あ", "り", " ", "ま", "す", "。" } - )] - [InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { })] - public void WordWrap_PreserveTrailingSpaces_True_Wide_Runes ( - string text, - int maxWidth, - int widthOffset, - IEnumerable resultLines - ) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - [Theory] - [InlineData ("A sentence has words. ", 3, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds.", " " })] - [InlineData ( - "A sentence has words. ", - 3, - new [] { "A ", " ", "sen", "ten", "ce ", " ", " ", " ", "has", " ", "wor", "ds.", " " } - )] - public void WordWrap_PreserveTrailingSpaces_True_With_Simple_Runes_Width_3 ( - string text, - int width, - IEnumerable resultLines - ) - { - List wrappedLines = TextFormatter.WordWrapText (text, width, true); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - - foreach (string line in wrappedLines) - { - breakLines += $"{line}{Environment.NewLine}"; - } - - var expected = string.Empty; - - foreach (string line in resultLines) - { - expected += $"{line}{Environment.NewLine}"; - } - - Assert.Equal (expected, breakLines); - - // Double space Complex example - this is how VS 2022 does it - //text = "A sentence has words. "; - //breakLines = ""; - //wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true); - //foreach (var line in wrappedLines) { - // breakLines += $"{line}{Environment.NewLine}"; - //} - //expected = "A " + Environment.NewLine + - // " se" + Environment.NewLine + - // " nt" + Environment.NewLine + - // " en" + Environment.NewLine + - // " ce" + Environment.NewLine + - // " " + Environment.NewLine + - // " " + Environment.NewLine + - // " " + Environment.NewLine + - // " ha" + Environment.NewLine + - // " s " + Environment.NewLine + - // " wo" + Environment.NewLine + - // " rd" + Environment.NewLine + - // " s." + Environment.NewLine; - //Assert.Equal (expected, breakLines); - } - - [Theory] - [InlineData ("A sentence\t\t\t has words.", 14, -10, new [] { "A sentence\t", "\t\t has ", "words." })] - [InlineData ( - "A sentence\t\t\t has words.", - 8, - -16, - new [] { "A ", "sentence", "\t\t", "\t ", "has ", "words." } - )] - [InlineData ( - "A sentence\t\t\t has words.", - 3, - -21, - new [] { "A ", "sen", "ten", "ce", "\t", "\t", "\t", " ", "has", " ", "wor", "ds." } - )] - [InlineData ( - "A sentence\t\t\t has words.", - 2, - -22, - new [] { "A ", "se", "nt", "en", "ce", "\t", "\t", "\t", " ", "ha", "s ", "wo", "rd", "s." } - )] - [InlineData ( - "A sentence\t\t\t has words.", - 1, - -23, - new [] - { - "A", - " ", - "s", - "e", - "n", - "t", - "e", - "n", - "c", - "e", - "\t", - "\t", - "\t", - " ", - "h", - "a", - "s", - " ", - "w", - "o", - "r", - "d", - "s", - "." - } - )] - public void WordWrap_PreserveTrailingSpaces_True_With_Tab ( - string text, - int maxWidth, - int widthOffset, - IEnumerable resultLines, - int tabWidth = 4 - ) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true, tabWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - [Theory] - [InlineData ("Constantinople", 14, 0, new [] { "Constantinople" })] - [InlineData ("Constantinople", 12, -2, new [] { "Constantinop", "le" })] - [InlineData ("Constantinople", 9, -5, new [] { "Constanti", "nople" })] - [InlineData ("Constantinople", 7, -7, new [] { "Constan", "tinople" })] - [InlineData ("Constantinople", 5, -9, new [] { "Const", "antin", "ople" })] - [InlineData ("Constantinople", 4, -10, new [] { "Cons", "tant", "inop", "le" })] - [InlineData ( - "Constantinople", - 1, - -13, - new [] { "C", "o", "n", "s", "t", "a", "n", "t", "i", "n", "o", "p", "l", "e" } - )] - public void WordWrap_SingleWordLine ( - string text, - int maxWidth, - int widthOffset, - IEnumerable resultLines - ) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - [Theory] - [InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 20, 0, new [] { "This\u00A0is\u00A0a\u00A0sentence." })] - [InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 19, -1, new [] { "This\u00A0is\u00A0a\u00A0sentence." })] - [InlineData ( - "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence.", - 19, - 0, - new [] { "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence." } - )] - public void WordWrap_Unicode_2LinesWithNonBreakingSpace ( - string text, - int maxWidth, - int widthOffset, - IEnumerable resultLines - ) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - [Theory] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 19, 0, new [] { "This\u00A0is\u00A0a\u00A0sentence." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 18, -1, new [] { "This\u00A0is\u00A0a\u00A0sentence", "." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 17, -2, new [] { "This\u00A0is\u00A0a\u00A0sentenc", "e." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 14, -5, new [] { "This\u00A0is\u00A0a\u00A0sent", "ence." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 10, -9, new [] { "This\u00A0is\u00A0a\u00A0", "sentence." })] - [InlineData ( - "This\u00A0is\u00A0a\u00A0sentence.", - 7, - -12, - new [] { "This\u00A0is", "\u00A0a\u00A0sent", "ence." } - )] - [InlineData ( - "This\u00A0is\u00A0a\u00A0sentence.", - 5, - -14, - new [] { "This\u00A0", "is\u00A0a\u00A0", "sente", "nce." } - )] - [InlineData ( - "This\u00A0is\u00A0a\u00A0sentence.", - 1, - -18, - new [] - { - "T", "h", "i", "s", "\u00A0", "i", "s", "\u00A0", "a", "\u00A0", "s", "e", "n", "t", "e", "n", "c", "e", "." - } - )] - public void WordWrap_Unicode_LineWithNonBreakingSpace ( - string text, - int maxWidth, - int widthOffset, - IEnumerable resultLines - ) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - [Theory] - [InlineData ( - "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", - 51, - 0, - new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" } - )] - [InlineData ( - "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", - 50, - -1, - new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" } - )] - [InlineData ( - "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", - 46, - -5, - new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" } - )] - [InlineData ( - "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", - 26, - -25, - new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" } - )] - [InlineData ( - "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", - 17, - -34, - new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" } - )] - [InlineData ( - "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", - 13, - -38, - new [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" } - )] - [InlineData ( - "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", - 1, - -50, - new [] - { - "ก", - "ข", - "ฃ", - "ค", - "ฅ", - "ฆ", - "ง", - "จ", - "ฉ", - "ช", - "ซ", - "ฌ", - "ญ", - "ฎ", - "ฏ", - "ฐ", - "ฑ", - "ฒ", - "ณ", - "ด", - "ต", - "ถ", - "ท", - "ธ", - "น", - "บ", - "ป", - "ผ", - "ฝ", - "พ", - "ฟ", - "ภ", - "ม", - "ย", - "ร", - "ฤ", - "ล", - "ฦ", - "ว", - "ศ", - "ษ", - "ส", - "ห", - "ฬ", - "อ", - "ฮ", - "ฯ", - "ะั", - "า", - "ำ" - } - )] - public void WordWrap_Unicode_SingleWordLine ( - string text, - int maxWidth, - int widthOffset, - IEnumerable resultLines - ) - { - List wrappedLines; - - IEnumerable zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0); - Assert.Single (zeroWidth); - Assert.Equal ('ั', zeroWidth.ElementAt (0).Value); - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth - >= (wrappedLines.Count > 0 - ? wrappedLines.Max ( - l => l.GetRuneCount () - + zeroWidth.Count () - - 1 - + widthOffset - ) - : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - /// WordWrap strips CRLF - [Theory] - [InlineData ( - "A sentence has words.\nA paragraph has lines.", - 44, - 0, - new [] { "A sentence has words.A paragraph has lines." } - )] - [InlineData ( - "A sentence has words.\nA paragraph has lines.", - 43, - -1, - new [] { "A sentence has words.A paragraph has lines." } - )] - [InlineData ( - "A sentence has words.\nA paragraph has lines.", - 38, - -6, - new [] { "A sentence has words.A paragraph has", "lines." } - )] - [InlineData ( - "A sentence has words.\nA paragraph has lines.", - 34, - -10, - new [] { "A sentence has words.A paragraph", "has lines." } - )] - [InlineData ( - "A sentence has words.\nA paragraph has lines.", - 27, - -17, - new [] { "A sentence has words.A", "paragraph has lines." } - )] - - // Unicode - [InlineData ( - "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", - 69, - 0, - new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." } - )] - [InlineData ( - "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", - 68, - -1, - new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." } - )] - [InlineData ( - "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", - 63, - -6, - new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has", "Линии." } - )] - [InlineData ( - "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", - 59, - -10, - new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт", "has Линии." } - )] - [InlineData ( - "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", - 52, - -17, - new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode", "Пункт has Линии." } - )] - public void WordWrap_WithNewLines (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - List wrappedLines; - - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) - ); - - Assert.True ( - expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) - ); - Assert.Equal (resultLines, wrappedLines); - } - - [SetupFakeDriver] - [Theory] - [InlineData ("A", 0, "")] - [InlineData ("A", 1, "A")] - [InlineData ("A", 2, "A")] - [InlineData ("AB", 1, "A")] - [InlineData ("AB", 2, "AB")] - [InlineData ("ABC", 3, "ABC")] - [InlineData ("ABC", 4, "ABC")] - [InlineData ("ABC", 6, "ABC")] - public void Draw_Horizontal_Left (string text, int width, string expectedText) - - { - TextFormatter tf = new () - { - Text = text, - Alignment = Alignment.Start - }; - - tf.ConstrainToWidth = width; - tf.ConstrainToHeight = 1; - tf.Draw (new (0, 0, width, 1), Attribute.Default, Attribute.Default); - - TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); - } - - [SetupFakeDriver] - [Theory] - [InlineData ("A", 0, "")] - [InlineData ("A", 1, "A")] - [InlineData ("A", 2, " A")] - [InlineData ("AB", 1, "B")] - [InlineData ("AB", 2, "AB")] - [InlineData ("ABC", 3, "ABC")] - [InlineData ("ABC", 4, " ABC")] - [InlineData ("ABC", 6, " ABC")] - public void Draw_Horizontal_Right (string text, int width, string expectedText) - { - TextFormatter tf = new () - { - Text = text, - Alignment = Alignment.End - }; - - tf.ConstrainToWidth = width; - tf.ConstrainToHeight = 1; - - tf.Draw (new (Point.Empty, new (width, 1)), Attribute.Default, Attribute.Default); - TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); - } + public static IEnumerable CMGlyphs => + new List { new object [] { $"{CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}", 16, 15 } }; [SetupFakeDriver] [Theory] @@ -3075,340 +295,52 @@ ssb [SetupFakeDriver] [Theory] - [InlineData ("A", 5, 5, "A")] - [InlineData ( - "AB12", - 5, - 5, - @" -A -B -1 -2")] - [InlineData ( - "AB\n12", - 5, - 5, - @" -A1 -B2")] - [InlineData ("", 5, 1, "")] - [InlineData ( - "Hello Worlds", - 1, - 12, - @" -H -e -l -l -o - -W -o -r -l -d -s")] - [InlineData ( - "Hello Worlds", - 1, - 12, - @" -H -e -l -l -o - -W -o -r -l -d -s")] - [InlineData ("Hello Worlds", 12, 1, @"HelloWorlds")] - public void Draw_Vertical_TopBottom_LeftRight (string text, int width, int height, string expectedText) + [InlineData ("A", 0, "")] + [InlineData ("A", 1, "A")] + [InlineData ("A", 2, "A")] + [InlineData ("AB", 1, "A")] + [InlineData ("AB", 2, "AB")] + [InlineData ("ABC", 3, "ABC")] + [InlineData ("ABC", 4, "ABC")] + [InlineData ("ABC", 6, "ABC")] + public void Draw_Horizontal_Left (string text, int width, string expectedText) + { TextFormatter tf = new () { Text = text, - Direction = TextDirection.TopBottom_LeftRight + Alignment = Alignment.Start }; tf.ConstrainToWidth = width; - tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, 20, 20), Attribute.Default, Attribute.Default); + tf.ConstrainToHeight = 1; + tf.Draw (new (0, 0, width, 1), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } [SetupFakeDriver] [Theory] - [InlineData ("Hello World", 15, 1, "Hello World")] - [InlineData ( - "Well Done\nNice Work", - 15, - 2, - @" -Well Done -Nice Work")] - [InlineData ("你好 世界", 15, 1, "你好 世界")] - [InlineData ( - "做 得好\n幹 得好", - 15, - 2, - @" -做 得好 -幹 得好")] - public void Justify_Horizontal (string text, int width, int height, string expectedText) + [InlineData ("A", 0, "")] + [InlineData ("A", 1, "A")] + [InlineData ("A", 2, " A")] + [InlineData ("AB", 1, "B")] + [InlineData ("AB", 2, "AB")] + [InlineData ("ABC", 3, "ABC")] + [InlineData ("ABC", 4, " ABC")] + [InlineData ("ABC", 6, " ABC")] + public void Draw_Horizontal_Right (string text, int width, string expectedText) { TextFormatter tf = new () { Text = text, - Alignment = Alignment.Fill, - ConstrainToSize = new Size (width, height), - MultiLine = true - }; - - tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); - - TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); - } - - [SetupFakeDriver] - [Theory] - [InlineData ("Hello World", 1, 15, "H\ne\nl\nl\no\n \n \n \n \n \nW\no\nr\nl\nd")] - [InlineData ( - "Well Done\nNice Work", - 2, - 15, - @" -WN -ei -lc -le - - - - - - - -DW -oo -nr -ek")] - [InlineData ("你好 世界", 2, 15, "你\n好\n \n \n \n \n \n \n \n \n \n \n \n世\n界")] - [InlineData ( - "做 得好\n幹 得好", - 4, - 15, - @" -做幹 - - - - - - - - - - - - -得得 -好好")] - public void Justify_Vertical (string text, int width, int height, string expectedText) - { - TextFormatter tf = new () - { - Text = text, - Direction = TextDirection.TopBottom_LeftRight, - VerticalAlignment = Alignment.Fill, - ConstrainToSize = new Size (width, height), - MultiLine = true - }; - - tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); - - TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); - } - - [SetupFakeDriver] - [Theory] - [InlineData ("A", 0, 1, "", 0)] - [InlineData ("A", 1, 1, "A", 0)] - [InlineData ("A", 2, 2, " A", 1)] - [InlineData ("AB", 1, 1, "B", 0)] - [InlineData ("AB", 2, 2, " A\n B", 0)] - [InlineData ("ABC", 3, 2, " B\n C", 0)] - [InlineData ("ABC", 4, 2, " B\n C", 0)] - [InlineData ("ABC", 6, 2, " B\n C", 0)] - [InlineData ("こんにちは", 0, 1, "", 0)] - [InlineData ("こんにちは", 1, 0, "", 0)] - [InlineData ("こんにちは", 1, 1, "", 0)] - [InlineData ("こんにちは", 2, 1, "は", 0)] - [InlineData ("こんにちは", 2, 2, "ち\nは", 0)] - [InlineData ("こんにちは", 2, 3, "に\nち\nは", 0)] - [InlineData ("こんにちは", 2, 4, "ん\nに\nち\nは", 0)] - [InlineData ("こんにちは", 2, 5, "こ\nん\nに\nち\nは", 0)] - [InlineData ("こんにちは", 2, 6, "こ\nん\nに\nち\nは", 1)] - [InlineData ("ABCD\nこんにちは", 4, 7, " こ\n Aん\n Bに\n Cち\n Dは", 2)] - [InlineData ("こんにちは\nABCD", 3, 7, "こ \nんA\nにB\nちC\nはD", 2)] - public void Draw_Vertical_Bottom_Horizontal_Right (string text, int width, int height, string expectedText, int expectedY) - { - TextFormatter tf = new () - { - Text = text, - Alignment = Alignment.End, - Direction = TextDirection.TopBottom_LeftRight, - VerticalAlignment = Alignment.End + Alignment = Alignment.End }; tf.ConstrainToWidth = width; - tf.ConstrainToHeight = height; - - tf.Draw (new (Point.Empty, new (width, height)), Attribute.Default, Attribute.Default); - Rectangle rect = TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); - Assert.Equal (expectedY, rect.Y); - } - - [SetupFakeDriver] - [Theory] - [InlineData ("A", 5, "A")] - [InlineData ( - "AB12", - 5, - @" -A -B -1 -2")] - [InlineData ( - "AB\n12", - 5, - @" -A1 -B2")] - [InlineData ("", 1, "")] - [InlineData ( - "AB1 2", - 2, - @" -A12 -B ")] - [InlineData ( - "こんにちは", - 1, - @" -こん")] - [InlineData ( - "こんにちは", - 2, - @" -こに -んち")] - [InlineData ( - "こんにちは", - 5, - @" -こ -ん -に -ち -は")] - public void Draw_Vertical_TopBottom_LeftRight_Top (string text, int height, string expectedText) - { - TextFormatter tf = new () - { - Text = text, - Direction = TextDirection.TopBottom_LeftRight - }; - - tf.ConstrainToWidth = 5; - tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default); - - TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); - } - - [SetupFakeDriver] - [Theory] - - // The expectedY param is to probe that the expectedText param start at that Y coordinate - [InlineData ("A", 0, "", 0)] - [InlineData ("A", 1, "A", 0)] - [InlineData ("A", 2, "A", 0)] - [InlineData ("A", 3, "A", 1)] - [InlineData ("AB", 1, "A", 0)] - [InlineData ("AB", 2, "A\nB", 0)] - [InlineData ("ABC", 2, "A\nB", 0)] - [InlineData ("ABC", 3, "A\nB\nC", 0)] - [InlineData ("ABC", 4, "A\nB\nC", 0)] - [InlineData ("ABC", 5, "A\nB\nC", 1)] - [InlineData ("ABC", 6, "A\nB\nC", 1)] - [InlineData ("ABC", 9, "A\nB\nC", 3)] - [InlineData ("ABCD", 2, "B\nC", 0)] - [InlineData ("こんにちは", 0, "", 0)] - [InlineData ("こんにちは", 1, "に", 0)] - [InlineData ("こんにちは", 2, "ん\nに", 0)] - [InlineData ("こんにちは", 3, "ん\nに\nち", 0)] - [InlineData ("こんにちは", 4, "こ\nん\nに\nち", 0)] - [InlineData ("こんにちは", 5, "こ\nん\nに\nち\nは", 0)] - [InlineData ("こんにちは", 6, "こ\nん\nに\nち\nは", 0)] - [InlineData ("ABCD\nこんにちは", 7, "Aこ\nBん\nCに\nDち\n は", 1)] - [InlineData ("こんにちは\nABCD", 7, "こA\nんB\nにC\nちD\nは ", 1)] - public void Draw_Vertical_TopBottom_LeftRight_Middle (string text, int height, string expectedText, int expectedY) - { - TextFormatter tf = new () - { - Text = text, - Direction = TextDirection.TopBottom_LeftRight, - VerticalAlignment = Alignment.Center - }; - - int width = text.ToRunes ().Max (r => r.GetColumns ()); - - if (text.Contains ("\n")) - { - width++; - } - - tf.ConstrainToWidth = width; - tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default); - - Rectangle rect = TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); - Assert.Equal (expectedY, rect.Y); - } - - [SetupFakeDriver] - [Theory] - [InlineData ("A", 1, 0, "")] - [InlineData ("A", 0, 1, "")] - [InlineData ("AB1 2", 2, 1, "2")] - [InlineData ("AB12", 5, 1, "21BA")] - [InlineData ("AB\n12", 5, 2, "BA\n21")] - [InlineData ("ABC 123 456", 7, 2, "654 321\nCBA ")] - [InlineData ("こんにちは", 1, 1, "")] - [InlineData ("こんにちは", 2, 1, "は")] - [InlineData ("こんにちは", 5, 1, "はち")] - [InlineData ("こんにちは", 10, 1, "はちにんこ")] - [InlineData ("こんにちは\nAB\n12", 10, 3, "はちにんこ\nBA \n21 ")] - public void Draw_Horizontal_RightLeft_TopBottom (string text, int width, int height, string expectedText) - { - TextFormatter tf = new () - { - Text = text, - Direction = TextDirection.RightLeft_TopBottom - }; - - tf.ConstrainToWidth = width; - tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); + tf.ConstrainToHeight = 1; + tf.Draw (new (Point.Empty, new (width, 1)), Attribute.Default, Attribute.Default); TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } @@ -3444,21 +376,21 @@ B ")] [Theory] [InlineData ("A", 1, 0, "")] [InlineData ("A", 0, 1, "")] - [InlineData ("AB1 2", 1, 2, "2")] - [InlineData ("AB12", 1, 5, "2\n1\nB\nA")] - [InlineData ("AB\n12", 2, 5, "B2\nA1")] - [InlineData ("ABC 123 456", 2, 7, "6C\n5B\n4A\n \n3 \n2 \n1 ")] + [InlineData ("AB1 2", 2, 1, "2")] + [InlineData ("AB12", 5, 1, "21BA")] + [InlineData ("AB\n12", 5, 2, "BA\n21")] + [InlineData ("ABC 123 456", 7, 2, "654 321\nCBA ")] [InlineData ("こんにちは", 1, 1, "")] [InlineData ("こんにちは", 2, 1, "は")] - [InlineData ("こんにちは", 2, 5, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは", 2, 10, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは\nAB\n12", 4, 10, "はB2\nちA1\nに \nん \nこ ")] - public void Draw_Vertical_BottomTop_LeftRight (string text, int width, int height, string expectedText) + [InlineData ("こんにちは", 5, 1, "はち")] + [InlineData ("こんにちは", 10, 1, "はちにんこ")] + [InlineData ("こんにちは\nAB\n12", 10, 3, "はちにんこ\nBA \n21 ")] + public void Draw_Horizontal_RightLeft_TopBottom (string text, int width, int height, string expectedText) { TextFormatter tf = new () { Text = text, - Direction = TextDirection.BottomTop_LeftRight + Direction = TextDirection.RightLeft_TopBottom }; tf.ConstrainToWidth = width; @@ -3468,70 +400,6 @@ B ")] TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } - [SetupFakeDriver] - [Theory] - [InlineData ("A", 1, 0, "")] - [InlineData ("A", 0, 1, "")] - [InlineData ("AB1 2", 1, 2, "2")] - [InlineData ("AB12", 1, 5, "2\n1\nB\nA")] - [InlineData ("AB\n12", 2, 5, "2B\n1A")] - [InlineData ("ABC 123 456", 2, 7, "C6\nB5\nA4\n \n 3\n 2\n 1")] - [InlineData ("こんにちは", 1, 1, "")] - [InlineData ("こんにちは", 2, 1, "は")] - [InlineData ("こんにちは", 2, 5, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは", 2, 10, "は\nち\nに\nん\nこ")] - [InlineData ("こんにちは\nAB\n12", 4, 10, "2Bは\n1Aち\n に\n ん\n こ")] - public void Draw_Vertical_BottomTop_RightLeft (string text, int width, int height, string expectedText) - { - TextFormatter tf = new () - { - Text = text, - Direction = TextDirection.BottomTop_RightLeft - }; - - tf.ConstrainToWidth = width; - tf.ConstrainToHeight = height; - tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); - - TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); - } - - // Draw tests - Note that these depend on View - - [Fact] - [TestRespondersDisposed] - public void Draw_Vertical_Throws_IndexOutOfRangeException_With_Negative_Bounds () - { - Application.Init (new FakeDriver ()); - - Toplevel top = new (); - - var view = new View { Y = -2, Height = 10, TextDirection = TextDirection.TopBottom_LeftRight, Text = "view" }; - top.Add (view); - - Application.Iteration += (s, a) => - { - Assert.Equal (-2, view.Y); - - Application.RequestStop (); - }; - - try - { - Application.Run (top); - } - catch (IndexOutOfRangeException ex) - { - // After the fix this exception will not be caught. - Assert.IsType (ex); - } - - top.Dispose (); - - // Shutdown must be called to safely clean up Application if Init has been called - Application.Shutdown (); - } - [SetupFakeDriver] [Theory] @@ -6950,6 +3818,1334 @@ B ")] TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); } + [SetupFakeDriver] + [Theory] + [InlineData ("A", 0, 1, "", 0)] + [InlineData ("A", 1, 1, "A", 0)] + [InlineData ("A", 2, 2, " A", 1)] + [InlineData ("AB", 1, 1, "B", 0)] + [InlineData ("AB", 2, 2, " A\n B", 0)] + [InlineData ("ABC", 3, 2, " B\n C", 0)] + [InlineData ("ABC", 4, 2, " B\n C", 0)] + [InlineData ("ABC", 6, 2, " B\n C", 0)] + [InlineData ("こんにちは", 0, 1, "", 0)] + [InlineData ("こんにちは", 1, 0, "", 0)] + [InlineData ("こんにちは", 1, 1, "", 0)] + [InlineData ("こんにちは", 2, 1, "は", 0)] + [InlineData ("こんにちは", 2, 2, "ち\nは", 0)] + [InlineData ("こんにちは", 2, 3, "に\nち\nは", 0)] + [InlineData ("こんにちは", 2, 4, "ん\nに\nち\nは", 0)] + [InlineData ("こんにちは", 2, 5, "こ\nん\nに\nち\nは", 0)] + [InlineData ("こんにちは", 2, 6, "こ\nん\nに\nち\nは", 1)] + [InlineData ("ABCD\nこんにちは", 4, 7, " こ\n Aん\n Bに\n Cち\n Dは", 2)] + [InlineData ("こんにちは\nABCD", 3, 7, "こ \nんA\nにB\nちC\nはD", 2)] + public void Draw_Vertical_Bottom_Horizontal_Right (string text, int width, int height, string expectedText, int expectedY) + { + TextFormatter tf = new () + { + Text = text, + Alignment = Alignment.End, + Direction = TextDirection.TopBottom_LeftRight, + VerticalAlignment = Alignment.End + }; + + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; + + tf.Draw (new (Point.Empty, new (width, height)), Attribute.Default, Attribute.Default); + Rectangle rect = TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + Assert.Equal (expectedY, rect.Y); + } + + [SetupFakeDriver] + [Theory] + [InlineData ("A", 1, 0, "")] + [InlineData ("A", 0, 1, "")] + [InlineData ("AB1 2", 1, 2, "2")] + [InlineData ("AB12", 1, 5, "2\n1\nB\nA")] + [InlineData ("AB\n12", 2, 5, "B2\nA1")] + [InlineData ("ABC 123 456", 2, 7, "6C\n5B\n4A\n \n3 \n2 \n1 ")] + [InlineData ("こんにちは", 1, 1, "")] + [InlineData ("こんにちは", 2, 1, "は")] + [InlineData ("こんにちは", 2, 5, "は\nち\nに\nん\nこ")] + [InlineData ("こんにちは", 2, 10, "は\nち\nに\nん\nこ")] + [InlineData ("こんにちは\nAB\n12", 4, 10, "はB2\nちA1\nに \nん \nこ ")] + public void Draw_Vertical_BottomTop_LeftRight (string text, int width, int height, string expectedText) + { + TextFormatter tf = new () + { + Text = text, + Direction = TextDirection.BottomTop_LeftRight + }; + + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); + + TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + } + + [SetupFakeDriver] + [Theory] + [InlineData ("A", 1, 0, "")] + [InlineData ("A", 0, 1, "")] + [InlineData ("AB1 2", 1, 2, "2")] + [InlineData ("AB12", 1, 5, "2\n1\nB\nA")] + [InlineData ("AB\n12", 2, 5, "2B\n1A")] + [InlineData ("ABC 123 456", 2, 7, "C6\nB5\nA4\n \n 3\n 2\n 1")] + [InlineData ("こんにちは", 1, 1, "")] + [InlineData ("こんにちは", 2, 1, "は")] + [InlineData ("こんにちは", 2, 5, "は\nち\nに\nん\nこ")] + [InlineData ("こんにちは", 2, 10, "は\nち\nに\nん\nこ")] + [InlineData ("こんにちは\nAB\n12", 4, 10, "2Bは\n1Aち\n に\n ん\n こ")] + public void Draw_Vertical_BottomTop_RightLeft (string text, int width, int height, string expectedText) + { + TextFormatter tf = new () + { + Text = text, + Direction = TextDirection.BottomTop_RightLeft + }; + + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); + + TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + } + + // Draw tests - Note that these depend on View + + [Fact] + [TestRespondersDisposed] + public void Draw_Vertical_Throws_IndexOutOfRangeException_With_Negative_Bounds () + { + Application.Init (new FakeDriver ()); + + Toplevel top = new (); + + var view = new View { Y = -2, Height = 10, TextDirection = TextDirection.TopBottom_LeftRight, Text = "view" }; + top.Add (view); + + Application.Iteration += (s, a) => + { + Assert.Equal (-2, view.Y); + + Application.RequestStop (); + }; + + try + { + Application.Run (top); + } + catch (IndexOutOfRangeException ex) + { + // After the fix this exception will not be caught. + Assert.IsType (ex); + } + + top.Dispose (); + + // Shutdown must be called to safely clean up Application if Init has been called + Application.Shutdown (); + } + + [SetupFakeDriver] + [Theory] + [InlineData ("A", 5, 5, "A")] + [InlineData ( + "AB12", + 5, + 5, + @" +A +B +1 +2")] + [InlineData ( + "AB\n12", + 5, + 5, + @" +A1 +B2")] + [InlineData ("", 5, 1, "")] + [InlineData ( + "Hello Worlds", + 1, + 12, + @" +H +e +l +l +o + +W +o +r +l +d +s")] + [InlineData ("Hello Worlds", 12, 1, @"HelloWorlds")] + public void Draw_Vertical_TopBottom_LeftRight (string text, int width, int height, string expectedText) + { + TextFormatter tf = new () + { + Text = text, + Direction = TextDirection.TopBottom_LeftRight + }; + + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; + tf.Draw (new (0, 0, 20, 20), Attribute.Default, Attribute.Default); + + TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + } + + [SetupFakeDriver] + [Theory] + + // The expectedY param is to probe that the expectedText param start at that Y coordinate + [InlineData ("A", 0, "", 0)] + [InlineData ("A", 1, "A", 0)] + [InlineData ("A", 2, "A", 0)] + [InlineData ("A", 3, "A", 1)] + [InlineData ("AB", 1, "A", 0)] + [InlineData ("AB", 2, "A\nB", 0)] + [InlineData ("ABC", 2, "A\nB", 0)] + [InlineData ("ABC", 3, "A\nB\nC", 0)] + [InlineData ("ABC", 4, "A\nB\nC", 0)] + [InlineData ("ABC", 5, "A\nB\nC", 1)] + [InlineData ("ABC", 6, "A\nB\nC", 1)] + [InlineData ("ABC", 9, "A\nB\nC", 3)] + [InlineData ("ABCD", 2, "B\nC", 0)] + [InlineData ("こんにちは", 0, "", 0)] + [InlineData ("こんにちは", 1, "に", 0)] + [InlineData ("こんにちは", 2, "ん\nに", 0)] + [InlineData ("こんにちは", 3, "ん\nに\nち", 0)] + [InlineData ("こんにちは", 4, "こ\nん\nに\nち", 0)] + [InlineData ("こんにちは", 5, "こ\nん\nに\nち\nは", 0)] + [InlineData ("こんにちは", 6, "こ\nん\nに\nち\nは", 0)] + [InlineData ("ABCD\nこんにちは", 7, "Aこ\nBん\nCに\nDち\n は", 1)] + [InlineData ("こんにちは\nABCD", 7, "こA\nんB\nにC\nちD\nは ", 1)] + public void Draw_Vertical_TopBottom_LeftRight_Middle (string text, int height, string expectedText, int expectedY) + { + TextFormatter tf = new () + { + Text = text, + Direction = TextDirection.TopBottom_LeftRight, + VerticalAlignment = Alignment.Center + }; + + int width = text.ToRunes ().Max (r => r.GetColumns ()); + + if (text.Contains ("\n")) + { + width++; + } + + tf.ConstrainToWidth = width; + tf.ConstrainToHeight = height; + tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default); + + Rectangle rect = TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + Assert.Equal (expectedY, rect.Y); + } + + [SetupFakeDriver] + [Theory] + [InlineData ("A", 5, "A")] + [InlineData ( + "AB12", + 5, + @" +A +B +1 +2")] + [InlineData ( + "AB\n12", + 5, + @" +A1 +B2")] + [InlineData ("", 1, "")] + [InlineData ( + "AB1 2", + 2, + @" +A12 +B ")] + [InlineData ( + "こんにちは", + 1, + @" +こん")] + [InlineData ( + "こんにちは", + 2, + @" +こに +んち")] + [InlineData ( + "こんにちは", + 5, + @" +こ +ん +に +ち +は")] + public void Draw_Vertical_TopBottom_LeftRight_Top (string text, int height, string expectedText) + { + TextFormatter tf = new () + { + Text = text, + Direction = TextDirection.TopBottom_LeftRight + }; + + tf.ConstrainToWidth = 5; + tf.ConstrainToHeight = height; + tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default); + + TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + } + + [Theory] + [InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misęrables")] + [InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")] + [InlineData ( + 4, + 4, + TextDirection.TopBottom_LeftRight, + @" +LMre +eias +ssb + ęl " + )] + public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected) + { + var driver = new FakeDriver (); + driver.Init (); + + var text = "Les Mise\u0328\u0301rables"; + + var tf = new TextFormatter (); + tf.Direction = textDirection; + tf.Text = text; + + Assert.True (tf.WordWrap); + + tf.ConstrainToSize = new (width, height); + + tf.Draw ( + new (0, 0, width, height), + new (ColorName.White, ColorName.Black), + new (ColorName.Blue, ColorName.Black), + default (Rectangle), + driver + ); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); + + driver.End (); + } + + [Fact] + [SetupFakeDriver] + public void FillRemaining_True_False () + { + ((FakeDriver)Application.Driver!).SetBufferSize (22, 5); + + Attribute [] attrs = + { + Attribute.Default, new (ColorName.Green, ColorName.BrightMagenta), + new (ColorName.Blue, ColorName.Cyan) + }; + var tf = new TextFormatter { ConstrainToSize = new (14, 3), Text = "Test\nTest long\nTest long long\n", MultiLine = true }; + + tf.Draw ( + new (1, 1, 19, 3), + attrs [1], + attrs [2]); + + Assert.False (tf.FillRemaining); + + TestHelpers.AssertDriverContentsWithFrameAre ( + @" + Test + Test long + Test long long", + _output); + + TestHelpers.AssertDriverAttributesAre ( + @" +000000000000000000000 +011110000000000000000 +011111111100000000000 +011111111111111000000 +000000000000000000000", + null, + attrs); + + tf.FillRemaining = true; + + tf.Draw ( + new (1, 1, 19, 3), + attrs [1], + attrs [2]); + + TestHelpers.AssertDriverAttributesAre ( + @" +000000000000000000000 +011111111111111111110 +011111111111111111110 +011111111111111111110 +000000000000000000000", + null, + attrs); + } + + [Theory] + [InlineData ("_k Before", true, 0, (KeyCode)'K')] // lower case should return uppercase Hotkey + [InlineData ("a_k Second", true, 1, (KeyCode)'K')] + [InlineData ("Last _k", true, 5, (KeyCode)'K')] + [InlineData ("After k_", false, -1, KeyCode.Null)] + [InlineData ("Multiple _k and _R", true, 9, (KeyCode)'K')] + [InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к')] // Lower case Cryllic K (к) + [InlineData ("_k Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results + [InlineData ("a_k Second", true, 1, (KeyCode)'K', true)] + [InlineData ("Last _k", true, 5, (KeyCode)'K', true)] + [InlineData ("After k_", false, -1, KeyCode.Null, true)] + [InlineData ("Multiple _k and _r", true, 9, (KeyCode)'K', true)] + [InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к', true)] // Cryllic K (К) + public void FindHotKey_AlphaLowerCase_Succeeds ( + string text, + bool expectedResult, + int expectedHotPos, + KeyCode expectedKey, + bool supportFirstUpperCase = false + ) + { + var hotKeySpecifier = (Rune)'_'; + + bool result = TextFormatter.FindHotKey ( + text, + hotKeySpecifier, + out int hotPos, + out Key hotKey, + supportFirstUpperCase + ); + + if (expectedResult) + { + Assert.True (result); + } + else + { + Assert.False (result); + } + + Assert.Equal (expectedResult, result); + Assert.Equal (expectedHotPos, hotPos); + Assert.Equal (expectedKey, hotKey); + } + + [Theory] + [InlineData ("_K Before", true, 0, (KeyCode)'K')] + [InlineData ("a_K Second", true, 1, (KeyCode)'K')] + [InlineData ("Last _K", true, 5, (KeyCode)'K')] + [InlineData ("After K_", false, -1, KeyCode.Null)] + [InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K')] + [InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К) + [InlineData ("_K Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results + [InlineData ("a_K Second", true, 1, (KeyCode)'K', true)] + [InlineData ("Last _K", true, 5, (KeyCode)'K', true)] + [InlineData ("After K_", false, -1, KeyCode.Null, true)] + [InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K', true)] + [InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К', true)] // Cryllic K (К) + public void FindHotKey_AlphaUpperCase_Succeeds ( + string text, + bool expectedResult, + int expectedHotPos, + KeyCode expectedKey, + bool supportFirstUpperCase = false + ) + { + var hotKeySpecifier = (Rune)'_'; + + bool result = TextFormatter.FindHotKey ( + text, + hotKeySpecifier, + out int hotPos, + out Key hotKey, + supportFirstUpperCase + ); + + if (expectedResult) + { + Assert.True (result); + } + else + { + Assert.False (result); + } + + Assert.Equal (expectedResult, result); + Assert.Equal (expectedHotPos, hotPos); + Assert.Equal (expectedKey, hotKey); + } + + [Theory] + [InlineData (null)] + [InlineData ("")] + [InlineData ("no hotkey")] + [InlineData ("No hotkey, Upper Case")] + [InlineData ("Non-english: Сохранить")] + public void FindHotKey_Invalid_ReturnsFalse (string text) + { + var hotKeySpecifier = (Rune)'_'; + var supportFirstUpperCase = false; + var hotPos = 0; + Key hotKey = KeyCode.Null; + var result = false; + + result = TextFormatter.FindHotKey ( + text, + hotKeySpecifier, + out hotPos, + out hotKey, + supportFirstUpperCase + ); + Assert.False (result); + Assert.Equal (-1, hotPos); + Assert.Equal (KeyCode.Null, hotKey); + } + + [Theory] + [InlineData ("\"k before")] + [InlineData ("ak second")] + [InlineData ("last k")] + [InlineData ("multiple k and r")] + [InlineData ("12345")] + [InlineData ("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")] // punctuation + [InlineData (" ~  s  gui.cs   master ↑10")] // ~IsLetterOrDigit + Unicode + [InlineData ("non-english: кдать")] // Lower case Cryllic K (к) + public void FindHotKey_Legacy_FirstUpperCase_NotFound_Returns_False (string text) + { + var supportFirstUpperCase = true; + + var hotKeySpecifier = (Rune)0; + + bool result = TextFormatter.FindHotKey ( + text, + hotKeySpecifier, + out int hotPos, + out Key hotKey, + supportFirstUpperCase + ); + Assert.False (result); + Assert.Equal (-1, hotPos); + Assert.Equal (KeyCode.Null, hotKey); + } + + [Theory] + [InlineData ("K Before", true, 0, (KeyCode)'K')] + [InlineData ("aK Second", true, 1, (KeyCode)'K')] + [InlineData ("last K", true, 5, (KeyCode)'K')] + [InlineData ("multiple K and R", true, 9, (KeyCode)'K')] + [InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К) + public void FindHotKey_Legacy_FirstUpperCase_Succeeds ( + string text, + bool expectedResult, + int expectedHotPos, + KeyCode expectedKey + ) + { + var supportFirstUpperCase = true; + + var hotKeySpecifier = (Rune)0; + + bool result = TextFormatter.FindHotKey ( + text, + hotKeySpecifier, + out int hotPos, + out Key hotKey, + supportFirstUpperCase + ); + + if (expectedResult) + { + Assert.True (result); + } + else + { + Assert.False (result); + } + + Assert.Equal (expectedResult, result); + Assert.Equal (expectedHotPos, hotPos); + Assert.Equal (expectedKey, hotKey); + } + + [Theory] + [InlineData ("_1 Before", true, 0, (KeyCode)'1')] // Digits + [InlineData ("a_1 Second", true, 1, (KeyCode)'1')] + [InlineData ("Last _1", true, 5, (KeyCode)'1')] + [InlineData ("After 1_", false, -1, KeyCode.Null)] + [InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1')] + [InlineData ("_1 Before", true, 0, (KeyCode)'1', true)] // Turn on FirstUpperCase and verify same results + [InlineData ("a_1 Second", true, 1, (KeyCode)'1', true)] + [InlineData ("Last _1", true, 5, (KeyCode)'1', true)] + [InlineData ("After 1_", false, -1, KeyCode.Null, true)] + [InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1', true)] + public void FindHotKey_Numeric_Succeeds ( + string text, + bool expectedResult, + int expectedHotPos, + KeyCode expectedKey, + bool supportFirstUpperCase = false + ) + { + var hotKeySpecifier = (Rune)'_'; + + bool result = TextFormatter.FindHotKey ( + text, + hotKeySpecifier, + out int hotPos, + out Key hotKey, + supportFirstUpperCase + ); + + if (expectedResult) + { + Assert.True (result); + } + else + { + Assert.False (result); + } + + Assert.Equal (expectedResult, result); + Assert.Equal (expectedHotPos, hotPos); + Assert.Equal (expectedKey, hotKey); + } + + [Theory] + [InlineData ("_\"k before", true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char + [InlineData ("\"_k before", true, KeyCode.K)] + [InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')] + [InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')] + [InlineData ( + "`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", + true, + (KeyCode)'=' + )] // BUGBUG: Not sure why this fails. Ignore the first and consider the second + [InlineData ("_ ~  s  gui.cs   master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode + [InlineData (" ~  s  gui.cs  _ master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode + [InlineData ("non-english: _кдать", true, (KeyCode)'к')] // Lower case Cryllic K (к) + public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected) + { + var hotKeySpecifier = (Rune)'_'; + + bool result = TextFormatter.FindHotKey (text, hotKeySpecifier, out int _, out Key hotKey); + Assert.Equal (found, result); + Assert.Equal (expected, hotKey); + } + + [Fact] + public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_End_Spaces_As_True () + { + Exception exception = Record.Exception ( + () => + TextFormatter.Format ( + "Some text", + 4, + Alignment.Start, + false, + true + ) + ); + Assert.Null (exception); + } + + [Theory] + [InlineData ( + "Hello world, how are you today? Pretty neat!", + 44, + 80, + "Hello world, how are you today? Pretty neat!" + )] + public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Horizontal ( + string text, + int runeCount, + int maxWidth, + string justifiedText + ) + { + Assert.Equal (runeCount, text.GetRuneCount ()); + + var fmtText = string.Empty; + + for (int i = text.GetRuneCount (); i < maxWidth; i++) + { + fmtText = TextFormatter.Format (text, i, Alignment.Fill, true) [0]; + Assert.Equal (i, fmtText.GetRuneCount ()); + char c = fmtText [^1]; + Assert.True (text.EndsWith (c)); + } + + Assert.Equal (justifiedText, fmtText); + } + + [Theory] + [InlineData ( + "Hello world, how are you today? Pretty neat!", + 44, + 80, + "Hello world, how are you today? Pretty neat!" + )] + public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Vertical ( + string text, + int runeCount, + int maxWidth, + string justifiedText + ) + { + Assert.Equal (runeCount, text.GetRuneCount ()); + + var fmtText = string.Empty; + + for (int i = text.GetRuneCount (); i < maxWidth; i++) + { + fmtText = TextFormatter.Format ( + text, + i, + Alignment.Fill, + false, + true, + 0, + TextDirection.TopBottom_LeftRight + ) [0]; + Assert.Equal (i, fmtText.GetRuneCount ()); + char c = fmtText [^1]; + Assert.True (text.EndsWith (c)); + } + + Assert.Equal (justifiedText, fmtText); + } + + [Theory] + [InlineData ("Truncate", 3, "Tru")] + [InlineData ("デモエムポンズ", 3, "デ")] + public void Format_Truncate_Simple_And_Wide_Runes (string text, int width, string expected) + { + List list = TextFormatter.Format (text, width, false, false); + Assert.Equal (expected, list [^1]); + } + + [Theory] + [MemberData (nameof (FormatEnvironmentNewLine))] + public void Format_With_PreserveTrailingSpaces_And_Without_PreserveTrailingSpaces ( + string text, + int width, + IEnumerable expected + ) + { + var preserveTrailingSpaces = false; + List formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces); + Assert.Equal (expected, formated); + + preserveTrailingSpaces = true; + formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces); + Assert.Equal (expected, formated); + } + + [Theory] + [InlineData ( + " A sentence has words. \n This is the second Line - 2. ", + 4, + -50, + Alignment.Start, + true, + false, + new [] { " A", "sent", "ence", "has", "word", "s. ", " Thi", "s is", "the", "seco", "nd", "Line", "- 2." }, + " Asentencehaswords. This isthesecondLine- 2." + )] + [InlineData ( + " A sentence has words. \n This is the second Line - 2. ", + 4, + -50, + Alignment.Start, + true, + true, + new [] + { + " A ", + "sent", + "ence", + " ", + "has ", + "word", + "s. ", + " ", + "This", + " is ", + "the ", + "seco", + "nd ", + "Line", + " - ", + "2. " + }, + " A sentence has words. This is the second Line - 2. " + )] + public void Format_WordWrap_PreserveTrailingSpaces ( + string text, + int maxWidth, + int widthOffset, + Alignment alignment, + bool wrap, + bool preserveTrailingSpaces, + IEnumerable resultLines, + string expectedWrappedText + ) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + List list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces); + Assert.Equal (list.Count, resultLines.Count ()); + Assert.Equal (resultLines, list); + var wrappedText = string.Empty; + + foreach (string txt in list) + { + wrappedText += txt; + } + + Assert.Equal (expectedWrappedText, wrappedText); + } + + public static IEnumerable FormatEnvironmentNewLine => + new List + { + new object [] + { + $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}", + 60, + new [] { "Line1", "Line2", "Line3" } + } + }; + + [Theory] + [InlineData ("Hello World", 11)] + [InlineData ("こんにちは世界", 14)] + public void GetColumns_Simple_And_Wide_Runes (string text, int width) { Assert.Equal (width, text.GetColumns ()); } + + [Theory] + [InlineData (new [] { "0123456789" }, 1)] + [InlineData (new [] { "Hello World" }, 1)] + [InlineData (new [] { "Hello", "World" }, 2)] + [InlineData (new [] { "こんにちは", "世界" }, 4)] + public void GetColumnsRequiredForVerticalText_List_GetsWidth (IEnumerable text, int expectedWidth) + { + Assert.Equal (expectedWidth, TextFormatter.GetColumnsRequiredForVerticalText (text.ToList ())); + } + + [Theory] + [InlineData (new [] { "Hello World" }, 1, 0, 1, 1)] + [InlineData (new [] { "Hello", "World" }, 2, 1, 1, 1)] + [InlineData (new [] { "こんにちは", "世界" }, 4, 1, 1, 2)] + public void GetColumnsRequiredForVerticalText_List_Simple_And_Wide_Runes ( + IEnumerable text, + int expectedWidth, + int index, + int length, + int expectedIndexWidth + ) + { + Assert.Equal (expectedWidth, TextFormatter.GetColumnsRequiredForVerticalText (text.ToList ())); + Assert.Equal (expectedIndexWidth, TextFormatter.GetColumnsRequiredForVerticalText (text.ToList (), index, length)); + } + + [Fact] + public void GetColumnsRequiredForVerticalText_List_With_Combining_Runes () + { + List text = new () { "Les Mis", "e\u0328\u0301", "rables" }; + Assert.Equal (1, TextFormatter.GetColumnsRequiredForVerticalText (text, 1, 1)); + } + + [Theory] + [InlineData ("Hello World", 6, 6)] + [InlineData ("こんにちは 世界", 6, 3)] + [MemberData (nameof (CMGlyphs))] + public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength) + { + List runes = text.ToRuneList (); + Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns)); + } + + [Theory] + [InlineData ("test", 3, 3)] + [InlineData ("test", 4, 4)] + [InlineData ("test", 10, 4)] + public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength) + { + List runes = text.ToRuneList (); + + Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns)); + } + + [Theory] + [InlineData ("Hello World", 6, 6)] + [InlineData ("こんにちは 世界", 6, 3)] + public void GetLengthThatFits_Simple_And_Wide_Runes (string text, int columns, int expectedLength) + { + Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns)); + } + + [Theory] + [InlineData ("test", 3, 3)] + [InlineData ("test", 4, 4)] + [InlineData ("test", 10, 4)] + [InlineData ("test", 1, 1)] + [InlineData ("test", 0, 0)] + [InlineData ("test", -1, 0)] + [InlineData (null, -1, 0)] + [InlineData ("", -1, 0)] + public void GetLengthThatFits_String (string text, int columns, int expectedLength) + { + Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns)); + } + + [Fact] + public void GetLengthThatFits_With_Combining_Runes () + { + var text = "Les Mise\u0328\u0301rables"; + Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14)); + } + + [Fact] + public void GetMaxColsForWidth_With_Combining_Runes () + { + List text = new () { "Les Mis", "e\u0328\u0301", "rables" }; + Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1)); + } + + //[Fact] + //public void GetWidestLineLength_With_Combining_Runes () + //{ + // var text = "Les Mise\u0328\u0301rables"; + // Assert.Equal (1, TextFormatter.GetWidestLineLength (text, 1, 1)); + //} + + [Fact] + public void Internal_Tests () + { + var tf = new TextFormatter (); + Assert.Equal (KeyCode.Null, tf.HotKey); + tf.HotKey = KeyCode.CtrlMask | KeyCode.Q; + Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, tf.HotKey); + } + + [SetupFakeDriver] + [Theory] + [InlineData ("Hello World", 15, 1, "Hello World")] + [InlineData ( + "Well Done\nNice Work", + 15, + 2, + @" +Well Done +Nice Work")] + [InlineData ("你好 世界", 15, 1, "你好 世界")] + [InlineData ( + "做 得好\n幹 得好", + 15, + 2, + @" +做 得好 +幹 得好")] + public void Justify_Horizontal (string text, int width, int height, string expectedText) + { + TextFormatter tf = new () + { + Text = text, + Alignment = Alignment.Fill, + ConstrainToSize = new Size (width, height), + MultiLine = true + }; + + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); + + TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + } + + [Theory] + [InlineData ("")] + [InlineData (null)] + [InlineData ("test")] + public void Justify_Invalid (string text) + { + Assert.Equal (text, TextFormatter.Justify (text, 0)); + Assert.Equal (text, TextFormatter.Justify (text, 0)); + Assert.Throws (() => TextFormatter.Justify (text, -1)); + } + + [Theory] + + // Even # of spaces + // 0123456789 + [InlineData ("012 456 89", "012 456 89", 10, 0, "+", true)] + [InlineData ("012 456 89", "012++456+89", 11, 1)] + [InlineData ("012 456 89", "012 456 89", 12, 2, "++", true)] + [InlineData ("012 456 89", "012+++456++89", 13, 3)] + [InlineData ("012 456 89", "012 456 89", 14, 4, "+++", true)] + [InlineData ("012 456 89", "012++++456+++89", 15, 5)] + [InlineData ("012 456 89", "012 456 89", 16, 6, "++++", true)] + [InlineData ("012 456 89", "012 456 89", 30, 20, "+++++++++++", true)] + [InlineData ("012 456 89", "012+++++++++++++456++++++++++++89", 33, 23)] + + // Odd # of spaces + // 01234567890123 + [InlineData ("012 456 89 end", "012 456 89 end", 14, 0, "+", true)] + [InlineData ("012 456 89 end", "012++456+89+end", 15, 1)] + [InlineData ("012 456 89 end", "012++456++89+end", 16, 2)] + [InlineData ("012 456 89 end", "012 456 89 end", 17, 3, "++", true)] + [InlineData ("012 456 89 end", "012+++456++89++end", 18, 4)] + [InlineData ("012 456 89 end", "012+++456+++89++end", 19, 5)] + [InlineData ("012 456 89 end", "012 456 89 end", 20, 6, "+++", true)] + [InlineData ("012 456 89 end", "012++++++++456++++++++89+++++++end", 34, 20)] + [InlineData ("012 456 89 end", "012+++++++++456+++++++++89++++++++end", 37, 23)] + + // Unicode + // Even # of chars + // 0123456789 + [InlineData ("пÑРвРÑ", "пÑРвРÑ", 10, 0, "+", true)] + [InlineData ("пÑРвРÑ", "пÑÐ++вÐ+Ñ", 11, 1)] + [InlineData ("пÑРвРÑ", "пÑРвРÑ", 12, 2, "++", true)] + [InlineData ("пÑРвРÑ", "пÑÐ+++вÐ++Ñ", 13, 3)] + [InlineData ("пÑРвРÑ", "пÑРвРÑ", 14, 4, "+++", true)] + [InlineData ("пÑРвРÑ", "пÑÐ++++вÐ+++Ñ", 15, 5)] + [InlineData ("пÑРвРÑ", "пÑРвРÑ", 16, 6, "++++", true)] + [InlineData ("пÑРвРÑ", "пÑРвРÑ", 30, 20, "+++++++++++", true)] + [InlineData ("пÑРвРÑ", "пÑÐ+++++++++++++вÐ++++++++++++Ñ", 33, 23)] + + // Unicode + // Odd # of chars + // 0123456789 + [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 10, 0, "+", true)] + [InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ+вÐ+Ñ", 11, 1)] + [InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ++вÐ+Ñ", 12, 2)] + [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 13, 3, "++", true)] + [InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ++вÐ++Ñ", 14, 4)] + [InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ+++вÐ++Ñ", 15, 5)] + [InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 16, 6, "+++", true)] + [InlineData ("Ð ÑРвРÑ", "Ð++++++++ÑÐ++++++++вÐ+++++++Ñ", 30, 20)] + [InlineData ("Ð ÑРвРÑ", "Ð+++++++++ÑÐ+++++++++вÐ++++++++Ñ", 33, 23)] + public void Justify_Sentence ( + string text, + string justifiedText, + int forceToWidth, + int widthOffset, + string replaceWith = null, + bool replace = false + ) + { + var fillChar = '+'; + + Assert.Equal (forceToWidth, text.GetRuneCount () + widthOffset); + + if (replace) + { + justifiedText = text.Replace (" ", replaceWith); + } + + Assert.Equal (justifiedText, TextFormatter.Justify (text, forceToWidth, fillChar)); + Assert.True (Math.Abs (forceToWidth - justifiedText.GetRuneCount ()) < text.Count (s => s == ' ')); + Assert.True (Math.Abs (forceToWidth - justifiedText.GetColumns ()) < text.Count (s => s == ' ')); + } + + [Theory] + [InlineData ("word")] // Even # of chars + [InlineData ("word.")] // Odd # of chars + [InlineData ("пÑивеÑ")] // Unicode (even #) + [InlineData ("пÑивеÑ.")] // Unicode (odd # of chars) + public void Justify_SingleWord (string text) + { + string justifiedText = text; + var fillChar = '+'; + + int width = text.GetRuneCount (); + Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); + width = text.GetRuneCount () + 1; + Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); + width = text.GetRuneCount () + 2; + Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); + width = text.GetRuneCount () + 10; + Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); + width = text.GetRuneCount () + 11; + Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar)); + } + + [SetupFakeDriver] + [Theory] + [InlineData ("Hello World", 1, 15, "H\ne\nl\nl\no\n \n \n \n \n \nW\no\nr\nl\nd")] + [InlineData ( + "Well Done\nNice Work", + 2, + 15, + @" +WN +ei +lc +le + + + + + + + +DW +oo +nr +ek")] + [InlineData ("你好 世界", 2, 15, "你\n好\n \n \n \n \n \n \n \n \n \n \n \n世\n界")] + [InlineData ( + "做 得好\n幹 得好", + 4, + 15, + @" +做幹 + + + + + + + + + + + + +得得 +好好")] + public void Justify_Vertical (string text, int width, int height, string expectedText) + { + TextFormatter tf = new () + { + Text = text, + Direction = TextDirection.TopBottom_LeftRight, + VerticalAlignment = Alignment.Fill, + ConstrainToSize = new Size (width, height), + MultiLine = true + }; + + tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default); + + TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + } + + [Theory] + [InlineData ("Single Line 界", 14)] + [InlineData ("First Line 界\nSecond Line 界\nThird Line 界\n", 14)] + public void MaxWidthLine_With_And_Without_Newlines (string text, int expected) { Assert.Equal (expected, TextFormatter.GetWidestLineLength (text)); } + + [Theory] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 0, + 0, + false, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 0, + 1, + false, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 1, + 0, + false, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 0, + 0, + true, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 0, + 1, + true, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 1, + 0, + true, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 6, + 5, + false, + new [] { "First " } + )] + [InlineData ("1\n2\n3\n4\n5\n6", 6, 5, false, new [] { "1 2 3 " })] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 6, + 5, + true, + new [] { "First ", "Second", "Third ", "Forty ", "Fiftee" } + )] + [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, false, new [] { "第一" })] + [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, true, new [] { "第一", "第二", "第三", "四十", "第十" })] + public void MultiLine_WordWrap_False_Horizontal_Direction ( + string text, + int maxWidth, + int maxHeight, + bool multiLine, + IEnumerable resultLines + ) + { + var tf = new TextFormatter + { + Text = text, ConstrainToSize = new (maxWidth, maxHeight), WordWrap = false, MultiLine = multiLine + }; + + Assert.False (tf.WordWrap); + Assert.True (tf.MultiLine == multiLine); + Assert.Equal (TextDirection.LeftRight_TopBottom, tf.Direction); + + List splitLines = tf.GetLines (); + Assert.Equal (splitLines.Count, resultLines.Count ()); + Assert.Equal (splitLines, resultLines); + } + + [Theory] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 0, + 0, + false, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 0, + 1, + false, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 1, + 0, + false, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 0, + 0, + true, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 0, + 1, + true, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 1, + 0, + true, + new [] { "" } + )] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 6, + 5, + false, + new [] { "First" } + )] + [InlineData ("1\n2\n3\n4\n5\n6", 6, 5, false, new [] { "1 2 3" })] + [InlineData ( + "First Line\nSecond Line\nThird Line\nForty Line\nFifteenth Line\nSeventy Line", + 6, + 5, + true, + new [] { "First", "Secon", "Third", "Forty", "Fifte", "Seven" } + )] + [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, false, new [] { "第一行 第" })] + [InlineData ("第一行\n第二行\n第三行\n四十行\n第十五行\n七十行", 5, 5, true, new [] { "第一行", "第二行" })] + public void MultiLine_WordWrap_False_Vertical_Direction ( + string text, + int maxWidth, + int maxHeight, + bool multiLine, + IEnumerable resultLines + ) + { + var tf = new TextFormatter + { + Text = text, + ConstrainToSize = new (maxWidth, maxHeight), + WordWrap = false, + MultiLine = multiLine, + Direction = TextDirection.TopBottom_LeftRight + }; + + Assert.False (tf.WordWrap); + Assert.True (tf.MultiLine == multiLine); + Assert.Equal (TextDirection.TopBottom_LeftRight, tf.Direction); + + List splitLines = tf.GetLines (); + Assert.Equal (splitLines.Count, resultLines.Count ()); + Assert.Equal (splitLines, resultLines); + } + + [Fact] + public void NeedsFormat_Sets () + { + var testText = "test"; + var testBounds = new Rectangle (0, 0, 100, 1); + var tf = new TextFormatter (); + + tf.Text = "test"; + Assert.True (tf.NeedsFormat); // get_Lines causes a Format + Assert.NotEmpty (tf.GetLines ()); + Assert.False (tf.NeedsFormat); // get_Lines causes a Format + Assert.Equal (testText, tf.Text); + tf.Draw (testBounds, new (), new ()); + Assert.False (tf.NeedsFormat); + + tf.ConstrainToSize = new (1, 1); + Assert.True (tf.NeedsFormat); + Assert.NotEmpty (tf.GetLines ()); + Assert.False (tf.NeedsFormat); // get_Lines causes a Format + + tf.Alignment = Alignment.Center; + Assert.True (tf.NeedsFormat); + Assert.NotEmpty (tf.GetLines ()); + Assert.False (tf.NeedsFormat); // get_Lines causes a Format + } + // Test that changing TextFormatter does not impact View dimensions if Dim.Auto is not in play [Fact] public void Not_Used_TextFormatter_Does_Not_Change_View_Size () @@ -6998,6 +5194,1835 @@ B ")] Assert.Equal (Size.Empty, view.Frame.Size); } + [Theory] + [InlineData ("", -1, Alignment.Start, false, 0)] + [InlineData (null, 0, Alignment.Start, false, 1)] + [InlineData (null, 0, Alignment.Start, true, 1)] + [InlineData ("", 0, Alignment.Start, false, 1)] + [InlineData ("", 0, Alignment.Start, true, 1)] + public void Reformat_Invalid (string text, int maxWidth, Alignment alignment, bool wrap, int linesCount) + { + if (maxWidth < 0) + { + Assert.Throws ( + () => + TextFormatter.Format (text, maxWidth, alignment, wrap) + ); + } + else + { + List list = TextFormatter.Format (text, maxWidth, alignment, wrap); + Assert.NotEmpty (list); + Assert.True (list.Count == linesCount); + Assert.Equal (string.Empty, list [0]); + } + } + + [Theory] + [InlineData ("A sentence has words.\nLine 2.", 0, -29, Alignment.Start, false, 1, true)] + [InlineData ("A sentence has words.\nLine 2.", 1, -28, Alignment.Start, false, 1, false)] + [InlineData ("A sentence has words.\nLine 2.", 5, -24, Alignment.Start, false, 1, false)] + [InlineData ("A sentence has words.\nLine 2.", 28, -1, Alignment.Start, false, 1, false)] + + // no clip + [InlineData ("A sentence has words.\nLine 2.", 29, 0, Alignment.Start, false, 1, false)] + [InlineData ("A sentence has words.\nLine 2.", 30, 1, Alignment.Start, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, Alignment.Start, false, 1, true)] + [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, Alignment.Start, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, Alignment.Start, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, Alignment.Start, false, 1, false, 1)] + [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, Alignment.Start, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, Alignment.Start, false, 1, false)] + public void Reformat_NoWordrap_NewLines_MultiLine_False ( + string text, + int maxWidth, + int widthOffset, + Alignment alignment, + bool wrap, + int linesCount, + bool stringEmpty, + int clipWidthOffset = 0 + ) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset; + List list = TextFormatter.Format (text, maxWidth, alignment, wrap); + Assert.NotEmpty (list); + Assert.True (list.Count == linesCount); + + if (stringEmpty) + { + Assert.Equal (string.Empty, list [0]); + } + else + { + Assert.NotEqual (string.Empty, list [0]); + } + + if (text.Contains ("\r\n") && maxWidth > 0) + { + Assert.Equal ( + StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]) + .Replace ("\r\n", " "), + list [0] + ); + } + else if (text.Contains ('\n') && maxWidth > 0) + { + Assert.Equal ( + StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]) + .Replace ("\n", " "), + list [0] + ); + } + else + { + Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]); + } + } + + [Theory] + [InlineData ("A sentence has words.\nLine 2.", 0, -29, Alignment.Start, false, 1, true, new [] { "" })] + [InlineData ( + "A sentence has words.\nLine 2.", + 1, + -28, + Alignment.Start, + false, + 2, + false, + new [] { "A", "L" } + )] + [InlineData ( + "A sentence has words.\nLine 2.", + 5, + -24, + Alignment.Start, + false, + 2, + false, + new [] { "A sen", "Line " } + )] + [InlineData ( + "A sentence has words.\nLine 2.", + 28, + -1, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + //// no clip + [InlineData ( + "A sentence has words.\nLine 2.", + 29, + 0, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + [InlineData ( + "A sentence has words.\nLine 2.", + 30, + 1, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, Alignment.Start, false, 1, true, new [] { "" })] + [InlineData ( + "A sentence has words.\r\nLine 2.", + 1, + -29, + Alignment.Start, + false, + 2, + false, + new [] { "A", "L" } + )] + [InlineData ( + "A sentence has words.\r\nLine 2.", + 5, + -25, + Alignment.Start, + false, + 2, + false, + new [] { "A sen", "Line " } + )] + [InlineData ( + "A sentence has words.\r\nLine 2.", + 29, + -1, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + [InlineData ( + "A sentence has words.\r\nLine 2.", + 30, + 0, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + [InlineData ( + "A sentence has words.\r\nLine 2.", + 31, + 1, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + public void Reformat_NoWordrap_NewLines_MultiLine_True ( + string text, + int maxWidth, + int widthOffset, + Alignment alignment, + bool wrap, + int linesCount, + bool stringEmpty, + IEnumerable resultLines + ) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + + List list = TextFormatter.Format ( + text, + maxWidth, + alignment, + wrap, + false, + 0, + TextDirection.LeftRight_TopBottom, + true + ); + Assert.NotEmpty (list); + Assert.True (list.Count == linesCount); + + if (stringEmpty) + { + Assert.Equal (string.Empty, list [0]); + } + else + { + Assert.NotEqual (string.Empty, list [0]); + } + + Assert.Equal (list, resultLines); + } + + [Theory] + [InlineData ("A sentence has words.\nLine 2.", 0, -29, Alignment.Start, false, 1, true, new [] { "" })] + [InlineData ( + "A sentence has words.\nLine 2.", + 1, + -28, + Alignment.Start, + false, + 2, + false, + new [] { "A", "L" } + )] + [InlineData ( + "A sentence has words.\nLine 2.", + 5, + -24, + Alignment.Start, + false, + 2, + false, + new [] { "A sen", "Line " } + )] + [InlineData ( + "A sentence has words.\nLine 2.", + 28, + -1, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + //// no clip + [InlineData ( + "A sentence has words.\nLine 2.", + 29, + 0, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + [InlineData ( + "A sentence has words.\nLine 2.", + 30, + 1, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, Alignment.Start, false, 1, true, new [] { "" })] + [InlineData ( + "A sentence has words.\r\nLine 2.", + 1, + -29, + Alignment.Start, + false, + 2, + false, + new [] { "A", "L" } + )] + [InlineData ( + "A sentence has words.\r\nLine 2.", + 5, + -25, + Alignment.Start, + false, + 2, + false, + new [] { "A sen", "Line " } + )] + [InlineData ( + "A sentence has words.\r\nLine 2.", + 29, + -1, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + [InlineData ( + "A sentence has words.\r\nLine 2.", + 30, + 0, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + [InlineData ( + "A sentence has words.\r\nLine 2.", + 31, + 1, + Alignment.Start, + false, + 2, + false, + new [] { "A sentence has words.", "Line 2." } + )] + public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical ( + string text, + int maxWidth, + int widthOffset, + Alignment alignment, + bool wrap, + int linesCount, + bool stringEmpty, + IEnumerable resultLines + ) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + + List list = TextFormatter.Format ( + text, + maxWidth, + alignment, + wrap, + false, + 0, + TextDirection.TopBottom_LeftRight, + true + ); + Assert.NotEmpty (list); + Assert.True (list.Count == linesCount); + + if (stringEmpty) + { + Assert.Equal (string.Empty, list [0]); + } + else + { + Assert.NotEqual (string.Empty, list [0]); + } + + Assert.Equal (list, resultLines); + } + + [Theory] + [InlineData ("", 0, 0, Alignment.Start, false, 1, true)] + [InlineData ("", 1, 1, Alignment.Start, false, 1, true)] + [InlineData ("A sentence has words.", 0, -21, Alignment.Start, false, 1, true)] + [InlineData ("A sentence has words.", 1, -20, Alignment.Start, false, 1, false)] + [InlineData ("A sentence has words.", 5, -16, Alignment.Start, false, 1, false)] + [InlineData ("A sentence has words.", 20, -1, Alignment.Start, false, 1, false)] + + // no clip + [InlineData ("A sentence has words.", 21, 0, Alignment.Start, false, 1, false)] + [InlineData ("A sentence has words.", 22, 1, Alignment.Start, false, 1, false)] + public void Reformat_NoWordrap_SingleLine ( + string text, + int maxWidth, + int widthOffset, + Alignment alignment, + bool wrap, + int linesCount, + bool stringEmpty + ) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + List list = TextFormatter.Format (text, maxWidth, alignment, wrap); + Assert.NotEmpty (list); + Assert.True (list.Count == linesCount); + + if (stringEmpty) + { + Assert.Equal (string.Empty, list [0]); + } + else + { + Assert.NotEqual (string.Empty, list [0]); + } + + Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]); + } + + [Theory] + + // Unicode + [InlineData ( + "\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", + 8, + -1, + Alignment.Start, + true, + false, + new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" } + )] + + // no clip + [InlineData ( + "\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", + 9, + 0, + Alignment.Start, + true, + false, + new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" } + )] + [InlineData ( + "\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", + 10, + 1, + Alignment.Start, + true, + false, + new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" } + )] + public void Reformat_Unicode_Wrap_Spaces_NewLines ( + string text, + int maxWidth, + int widthOffset, + Alignment alignment, + bool wrap, + bool preserveTrailingSpaces, + IEnumerable resultLines + ) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + List list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces); + Assert.Equal (list.Count, resultLines.Count ()); + Assert.Equal (resultLines, list); + } + + [Theory] + + // Unicode + // Even # of chars + // 0123456789 + [InlineData ("\u2660пÑРвРÑ", 10, -1, Alignment.Start, true, false, new [] { "\u2660пÑРвÐ", "Ñ" })] + + // no clip + [InlineData ("\u2660пÑРвРÑ", 11, 0, Alignment.Start, true, false, new [] { "\u2660пÑРвРÑ" })] + [InlineData ("\u2660пÑРвРÑ", 12, 1, Alignment.Start, true, false, new [] { "\u2660пÑРвРÑ" })] + + // Unicode + // Odd # of chars + // 0123456789 + [InlineData ("\u2660 ÑРвРÑ", 9, -1, Alignment.Start, true, false, new [] { "\u2660 ÑРвÐ", "Ñ" })] + + // no clip + [InlineData ("\u2660 ÑРвРÑ", 10, 0, Alignment.Start, true, false, new [] { "\u2660 ÑРвРÑ" })] + [InlineData ("\u2660 ÑРвРÑ", 11, 1, Alignment.Start, true, false, new [] { "\u2660 ÑРвРÑ" })] + public void Reformat_Unicode_Wrap_Spaces_No_NewLines ( + string text, + int maxWidth, + int widthOffset, + Alignment alignment, + bool wrap, + bool preserveTrailingSpaces, + IEnumerable resultLines + ) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + List list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces); + Assert.Equal (list.Count, resultLines.Count ()); + Assert.Equal (resultLines, list); + } + + [Theory] + + // Even # of spaces + // 0123456789 + [InlineData ("012 456 89", 0, -10, Alignment.Start, true, true, true, new [] { "" })] + [InlineData ( + "012 456 89", + 1, + -9, + Alignment.Start, + true, + true, + false, + new [] { "0", "1", "2", " ", "4", "5", "6", " ", "8", "9" }, + "01245689" + )] + [InlineData ("012 456 89", 5, -5, Alignment.Start, true, true, false, new [] { "012 ", "456 ", "89" })] + [InlineData ("012 456 89", 9, -1, Alignment.Start, true, true, false, new [] { "012 456 ", "89" })] + + // no clip + [InlineData ("012 456 89", 10, 0, Alignment.Start, true, true, false, new [] { "012 456 89" })] + [InlineData ("012 456 89", 11, 1, Alignment.Start, true, true, false, new [] { "012 456 89" })] + + // Odd # of spaces + // 01234567890123 + [InlineData ("012 456 89 end", 13, -1, Alignment.Start, true, true, false, new [] { "012 456 89 ", "end" })] + + // no clip + [InlineData ("012 456 89 end", 14, 0, Alignment.Start, true, true, false, new [] { "012 456 89 end" })] + [InlineData ("012 456 89 end", 15, 1, Alignment.Start, true, true, false, new [] { "012 456 89 end" })] + public void Reformat_Wrap_Spaces_No_NewLines ( + string text, + int maxWidth, + int widthOffset, + Alignment alignment, + bool wrap, + bool preserveTrailingSpaces, + bool stringEmpty, + IEnumerable resultLines, + string noSpaceText = "" + ) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + List list = TextFormatter.Format (text, maxWidth, alignment, wrap, preserveTrailingSpaces); + Assert.NotEmpty (list); + Assert.True (list.Count == resultLines.Count ()); + + if (stringEmpty) + { + Assert.Equal (string.Empty, list [0]); + } + else + { + Assert.NotEqual (string.Empty, list [0]); + } + + Assert.Equal (resultLines, list); + + if (maxWidth > 0) + { + // remove whitespace chars + if (maxWidth < 5) + { + expectedClippedWidth = text.GetRuneCount () - text.Sum (r => r == ' ' ? 1 : 0); + } + else + { + expectedClippedWidth = Math.Min ( + text.GetRuneCount (), + maxWidth - text.Sum (r => r == ' ' ? 1 : 0) + ); + } + + list = TextFormatter.Format (text, maxWidth, Alignment.Start, wrap); + + if (maxWidth == 1) + { + Assert.Equal (expectedClippedWidth, list.Count); + Assert.Equal (noSpaceText, string.Concat (list.ToArray ())); + } + + if (maxWidth > 1 && maxWidth < 10) + { + Assert.Equal ( + StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), + list [0] + ); + } + } + } + + [Theory] + [InlineData (null)] + [InlineData ("")] + [InlineData ("a")] + public void RemoveHotKeySpecifier_InValid_ReturnsOriginal (string text) + { + var hotKeySpecifier = (Rune)'_'; + + if (text == null) + { + Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier)); + Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier)); + Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier)); + } + else + { + Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier)); + Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier)); + Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier)); + } + } + + [Theory] + [InlineData ("all lower case", 0)] + [InlineData ("K Before", 0)] + [InlineData ("aK Second", 1)] + [InlineData ("Last K", 5)] + [InlineData ("fter K", 7)] + [InlineData ("Multiple K and R", 9)] + [InlineData ("Non-english: Кдать", 13)] + public void RemoveHotKeySpecifier_Valid_Legacy_ReturnsOriginal (string text, int hotPos) + { + var hotKeySpecifier = (Rune)'_'; + + Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier)); + } + + [Theory] + [InlineData ("_K Before", 0, "K Before")] + [InlineData ("a_K Second", 1, "aK Second")] + [InlineData ("Last _K", 5, "Last K")] + [InlineData ("After K_", 7, "After K")] + [InlineData ("Multiple _K and _R", 9, "Multiple K and _R")] + [InlineData ("Non-english: _Кдать", 13, "Non-english: Кдать")] + public void RemoveHotKeySpecifier_Valid_ReturnsStripped (string text, int hotPos, string expectedText) + { + var hotKeySpecifier = (Rune)'_'; + + Assert.Equal (expectedText, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier)); + } + + [Theory] + [InlineData ("test", 0, 't', "test")] + [InlineData ("test", 1, 'e', "test")] + [InlineData ("Ok", 0, 'O', "Ok")] + [InlineData ("[◦ Ok ◦]", 3, 'O', "[◦ Ok ◦]")] + [InlineData ("^k", 0, '^', "^k")] + public void ReplaceHotKeyWithTag (string text, int hotPos, uint tag, string expected) + { + var tf = new TextFormatter (); + List runes = text.ToRuneList (); + Rune rune; + + if (Rune.TryGetRuneAt (text, hotPos, out rune)) + { + Assert.Equal (rune, (Rune)tag); + } + + string result = TextFormatter.ReplaceHotKeyWithTag (text, hotPos); + Assert.Equal (result, expected); + Assert.Equal ((Rune)tag, result.ToRunes () [hotPos]); + Assert.Equal (text.GetRuneCount (), runes.Count); + Assert.Equal (text, StringExtensions.ToString (runes)); + } + + public static IEnumerable SplitEnvironmentNewLine => + new List + { + new object [] + { + $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界", + new [] { "First Line 界", "Second Line 界", "Third Line 界" } + }, + new object [] + { + $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界{Environment.NewLine}", + new [] { "First Line 界", "Second Line 界", "Third Line 界", "" } + } + }; + + [Theory] + [MemberData (nameof (SplitEnvironmentNewLine))] + public void SplitNewLine_Ending__With_Or_Without_NewLine_Probably_CRLF ( + string text, + IEnumerable expected + ) + { + List splited = TextFormatter.SplitNewLine (text); + Assert.Equal (expected, splited); + } + + [Theory] + [InlineData ( + "First Line 界\nSecond Line 界\nThird Line 界\n", + new [] { "First Line 界", "Second Line 界", "Third Line 界", "" } + )] + public void SplitNewLine_Ending_With_NewLine_Only_LF (string text, IEnumerable expected) + { + List splited = TextFormatter.SplitNewLine (text); + Assert.Equal (expected, splited); + } + + [Theory] + [InlineData ( + "First Line 界\nSecond Line 界\nThird Line 界", + new [] { "First Line 界", "Second Line 界", "Third Line 界" } + )] + public void SplitNewLine_Ending_Without_NewLine_Only_LF (string text, IEnumerable expected) + { + List splited = TextFormatter.SplitNewLine (text); + Assert.Equal (expected, splited); + } + + [Theory] + [InlineData ("New Test 你", 10, 10, 20320, 20320, 9, "你")] + [InlineData ("New Test \U0001d539", 10, 11, 120121, 55349, 9, "𝔹")] + public void String_Array_Is_Not_Always_Equal_ToRunes_Array ( + string text, + int runesLength, + int stringLength, + int runeValue, + int stringValue, + int index, + string expected + ) + { + Rune [] usToRunes = text.ToRunes (); + Assert.Equal (runesLength, usToRunes.Length); + Assert.Equal (stringLength, text.Length); + Assert.Equal (runeValue, usToRunes [index].Value); + Assert.Equal (stringValue, text [index]); + Assert.Equal (expected, usToRunes [index].ToString ()); + + if (char.IsHighSurrogate (text [index])) + { + // Rune array length isn't equal to string array + Assert.Equal (expected, new (new [] { text [index], text [index + 1] })); + } + else + { + // Rune array length is equal to string array + Assert.Equal (expected, text [index].ToString ()); + } + } + + [Theory] + [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] + [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] + [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] + [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] + public void TabWith_PreserveTrailingSpaces_False ( + int width, + int height, + TextDirection textDirection, + int tabWidth, + string expected + ) + { + var driver = new FakeDriver (); + driver.Init (); + + var text = "This is a \tTab"; + var tf = new TextFormatter (); + tf.Direction = textDirection; + tf.TabWidth = tabWidth; + tf.Text = text; + tf.ConstrainToWidth = 20; + tf.ConstrainToHeight = 20; + + Assert.True (tf.WordWrap); + Assert.False (tf.PreserveTrailingSpaces); + + tf.Draw ( + new (0, 0, width, height), + new (ColorName.White, ColorName.Black), + new (ColorName.Blue, ColorName.Black), + default (Rectangle), + driver + ); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); + + driver.End (); + } + + [Theory] + [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] + [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] + [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] + [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] + public void TabWith_PreserveTrailingSpaces_True ( + int width, + int height, + TextDirection textDirection, + int tabWidth, + string expected + ) + { + var driver = new FakeDriver (); + driver.Init (); + + var text = "This is a \tTab"; + var tf = new TextFormatter (); + + tf.Direction = textDirection; + tf.TabWidth = tabWidth; + tf.PreserveTrailingSpaces = true; + tf.Text = text; + tf.ConstrainToWidth = 20; + tf.ConstrainToHeight = 20; + + Assert.True (tf.WordWrap); + + tf.Draw ( + new (0, 0, width, height), + new (ColorName.White, ColorName.Black), + new (ColorName.Blue, ColorName.Black), + default (Rectangle), + driver + ); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); + + driver.End (); + } + + [Theory] + [InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")] + [InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")] + [InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")] + [InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")] + public void TabWith_WordWrap_True ( + int width, + int height, + TextDirection textDirection, + int tabWidth, + string expected + ) + { + var driver = new FakeDriver (); + driver.Init (); + + var text = "This is a \tTab"; + var tf = new TextFormatter (); + + tf.Direction = textDirection; + tf.TabWidth = tabWidth; + tf.WordWrap = true; + tf.Text = text; + tf.ConstrainToWidth = 20; + tf.ConstrainToHeight = 20; + + Assert.False (tf.PreserveTrailingSpaces); + + tf.Draw ( + new (0, 0, width, height), + new (ColorName.White, ColorName.Black), + new (ColorName.Blue, ColorName.Black), + default (Rectangle), + driver + ); + TestHelpers.AssertDriverContentsWithFrameAre (expected, _output, driver); + + driver.End (); + } + + [Theory] + [InlineData ("123456789", 3, "123")] + [InlineData ("Hello World", 8, "Hello Wo")] + public void TestClipOrPad_LongWord (string text, int fillPad, string expectedText) + { + // word is long but we want it to fill # space only + Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad)); + } + + [Theory] + [InlineData ("fff", 6, "fff ")] + [InlineData ("Hello World", 16, "Hello World ")] + public void TestClipOrPad_ShortWord (string text, int fillPad, string expectedText) + { + // word is short but we want it to fill # so it should be padded + Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad)); + } + + [Theory] + [InlineData ("你", TextDirection.LeftRight_TopBottom, 2, 1)] + [InlineData ("你", TextDirection.TopBottom_LeftRight, 2, 1)] + [InlineData ("你你", TextDirection.LeftRight_TopBottom, 4, 1)] + [InlineData ("你你", TextDirection.TopBottom_LeftRight, 2, 2)] + public void Text_Set_SizeIsCorrect (string text, TextDirection textDirection, int expectedWidth, int expectedHeight) + { + var tf = new TextFormatter { Direction = textDirection, Text = text }; + tf.ConstrainToWidth = 10; + tf.ConstrainToHeight = 10; + + Assert.Equal (new (expectedWidth, expectedHeight), tf.FormatAndGetSize ()); + } + + [Fact] + [SetupFakeDriver] + public void UICatalog_AboutBox_Text () + { + TextFormatter tf = new () + { + Text = UICatalogApp.GetAboutBoxMessage (), + Alignment = Alignment.Center, + VerticalAlignment = Alignment.Start, + WordWrap = false, + MultiLine = true, + HotKeySpecifier = (Rune)0xFFFF + }; + + Size tfSize = tf.FormatAndGetSize (); + Assert.Equal (new (58, 13), tfSize); + + ((FakeDriver)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height); + + Application.Driver.FillRect (Application.Screen, (Rune)'*'); + tf.Draw (Application.Screen, Attribute.Default, Attribute.Default); + + var expectedText = """ + ******UI Catalog: A comprehensive sample library for****** + ********************************************************** + _______ _ _ _____ _ + |__ __| (_) | | / ____| (_) + | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ + | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | + | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | + |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| + ********************************************************** + **********************v2 - Pre-Alpha********************** + ********************************************************** + **********https://github.com/gui-cs/Terminal.Gui********** + ********************************************************** + """; + + TestHelpers.AssertDriverContentsAre (expectedText.ReplaceLineEndings (), _output); + } + + [Fact] + public void WordWrap_BigWidth () + { + List wrappedLines; + + var text = "Constantinople"; + wrappedLines = TextFormatter.WordWrapText (text, 100); + Assert.True (wrappedLines.Count == 1); + Assert.Equal ("Constantinople", wrappedLines [0]); + } + + [Fact] + public void WordWrap_Invalid () + { + var text = string.Empty; + var width = 0; + + Assert.Empty (TextFormatter.WordWrapText (null, width)); + Assert.Empty (TextFormatter.WordWrapText (text, width)); + Assert.Throws (() => TextFormatter.WordWrapText (text, -1)); + } + + [Theory] + [InlineData ("A sentence has words.", 3, -18, new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] + [InlineData ( + "A sentence has words.", + 2, + -19, + new [] { "A", "se", "nt", "en", "ce", "ha", "s", "wo", "rd", "s." } + )] + [InlineData ( + "A sentence has words.", + 1, + -20, + new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." } + )] + public void WordWrap_Narrow_Default ( + string text, + int maxWidth, + int widthOffset, + IEnumerable resultLines + ) + { + // Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + + [Theory] + [InlineData ("A sentence has words.", 21, 0, new [] { "A sentence has words." })] + [InlineData ("A sentence has words.", 20, -1, new [] { "A sentence has", "words." })] + [InlineData ("A sentence has words.", 15, -6, new [] { "A sentence has", "words." })] + [InlineData ("A sentence has words.", 14, -7, new [] { "A sentence has", "words." })] + [InlineData ("A sentence has words.", 13, -8, new [] { "A sentence", "has words." })] + + // Unicode + [InlineData ( + "A Unicode sentence (пÑивеÑ) has words.", + 42, + 0, + new [] { "A Unicode sentence (пÑивеÑ) has words." } + )] + [InlineData ( + "A Unicode sentence (пÑивеÑ) has words.", + 41, + -1, + new [] { "A Unicode sentence (пÑивеÑ) has", "words." } + )] + [InlineData ( + "A Unicode sentence (пÑивеÑ) has words.", + 36, + -6, + new [] { "A Unicode sentence (пÑивеÑ) has", "words." } + )] + [InlineData ( + "A Unicode sentence (пÑивеÑ) has words.", + 35, + -7, + new [] { "A Unicode sentence (пÑивеÑ) has", "words." } + )] + [InlineData ( + "A Unicode sentence (пÑивеÑ) has words.", + 34, + -8, + new [] { "A Unicode sentence (пÑивеÑ)", "has words." } + )] + [InlineData ( + "A Unicode sentence (пÑивеÑ) has words.", + 25, + -17, + new [] { "A Unicode sentence", "(пÑивеÑ) has words." } + )] + public void WordWrap_NoNewLines_Default ( + string text, + int maxWidth, + int widthOffset, + IEnumerable resultLines + ) + { + // Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + + [Theory] + [InlineData ("これが最初の行です。 こんにちは世界。 これが2行目です。", 29, 0, new [] { "これが最初の行です。", "こんにちは世界。", "これが2行目です。" })] + public void WordWrap_PreserveTrailingSpaces_False_Unicode_Wide_Runes ( + string text, + int maxWidth, + int widthOffset, + IEnumerable resultLines + ) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + + [Theory] + [InlineData ("文に は言葉 があり ます。", 14, 0, new [] { "文に は言葉", "があり ます。" })] + [InlineData ("文に は言葉 があり ます。", 3, -11, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })] + [InlineData ("文に は言葉 があり ます。", 2, -12, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })] + [InlineData ( + "文に は言葉 があり ます。", + 1, + -13, + new [] { " ", " ", " " } + )] // Just Spaces; should result in a single space for each line + public void WordWrap_PreserveTrailingSpaces_False_Wide_Runes ( + string text, + int maxWidth, + int widthOffset, + IEnumerable resultLines + ) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + + [Theory] + [InlineData (null, 1, new string [] { })] // null input + [InlineData ("", 1, new string [] { })] // Empty input + [InlineData ("1 34", 1, new [] { "1", "3", "4" })] // Single Spaces + [InlineData ("1", 1, new [] { "1" })] // Short input + [InlineData ("12", 1, new [] { "1", "2" })] + [InlineData ("123", 1, new [] { "1", "2", "3" })] + [InlineData ("123456", 1, new [] { "1", "2", "3", "4", "5", "6" })] // No spaces + [InlineData (" ", 1, new [] { " " })] // Just Spaces; should result in a single space + [InlineData (" ", 1, new [] { " " })] + [InlineData (" ", 1, new [] { " ", " " })] + [InlineData (" ", 1, new [] { " ", " " })] + [InlineData ("12 456", 1, new [] { "1", "2", "4", "5", "6" })] // Single Spaces + [InlineData (" 2 456", 1, new [] { " ", "2", "4", "5", "6" })] // Leading spaces should be preserved. + [InlineData (" 2 456 8", 1, new [] { " ", "2", "4", "5", "6", "8" })] + [InlineData ( + "A sentence has words. ", + 1, + new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." } + )] // Complex example + [InlineData ("12 567", 1, new [] { "1", "2", " ", "5", "6", "7" })] // Double Spaces + [InlineData (" 3 567", 1, new [] { " ", "3", "5", "6", "7" })] // Double Leading spaces should be preserved. + [InlineData (" 3 678 1", 1, new [] { " ", "3", " ", "6", "7", "8", " ", "1" })] + [InlineData ("1 456", 1, new [] { "1", " ", "4", "5", "6" })] + [InlineData ( + "A sentence has words. ", + 1, + new [] + { + "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", "w", "o", "r", "d", "s", ".", " " + } + )] // Double space Complex example + public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_1 ( + string text, + int width, + IEnumerable resultLines + ) + { + List wrappedLines = TextFormatter.WordWrapText (text, width); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + + foreach (string line in wrappedLines) + { + breakLines += $"{line}{Environment.NewLine}"; + } + + var expected = string.Empty; + + foreach (string line in resultLines) + { + expected += $"{line}{Environment.NewLine}"; + } + + Assert.Equal (expected, breakLines); + } + + [Theory] + [InlineData (null, 3, new string [] { })] // null input + [InlineData ("", 3, new string [] { })] // Empty input + [InlineData ("1", 3, new [] { "1" })] // Short input + [InlineData ("12", 3, new [] { "12" })] + [InlineData ("123", 3, new [] { "123" })] + [InlineData ("123456", 3, new [] { "123", "456" })] // No spaces + [InlineData ("1234567", 3, new [] { "123", "456", "7" })] // No spaces + [InlineData (" ", 3, new [] { " " })] // Just Spaces; should result in a single space + [InlineData (" ", 3, new [] { " " })] + [InlineData (" ", 3, new [] { " " })] + [InlineData (" ", 3, new [] { " " })] + [InlineData ("12 456", 3, new [] { "12", "456" })] // Single Spaces + [InlineData (" 2 456", 3, new [] { " 2", "456" })] // Leading spaces should be preserved. + [InlineData (" 2 456 8", 3, new [] { " 2", "456", "8" })] + [InlineData ( + "A sentence has words. ", + 3, + new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." } + )] // Complex example + [InlineData ("12 567", 3, new [] { "12 ", "567" })] // Double Spaces + [InlineData (" 3 567", 3, new [] { " 3", "567" })] // Double Leading spaces should be preserved. + [InlineData (" 3 678 1", 3, new [] { " 3", " 67", "8 ", "1" })] + [InlineData ("1 456", 3, new [] { "1 ", "456" })] + [InlineData ( + "A sentence has words. ", + 3, + new [] { "A ", "sen", "ten", "ce ", " ", "has", "wor", "ds.", " " } + )] // Double space Complex example + public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_3 ( + string text, + int width, + IEnumerable resultLines + ) + { + List wrappedLines = TextFormatter.WordWrapText (text, width); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + + foreach (string line in wrappedLines) + { + breakLines += $"{line}{Environment.NewLine}"; + } + + var expected = string.Empty; + + foreach (string line in resultLines) + { + expected += $"{line}{Environment.NewLine}"; + } + + Assert.Equal (expected, breakLines); + } + + [Theory] + [InlineData (null, 50, new string [] { })] // null input + [InlineData ("", 50, new string [] { })] // Empty input + [InlineData ("1", 50, new [] { "1" })] // Short input + [InlineData ("12", 50, new [] { "12" })] + [InlineData ("123", 50, new [] { "123" })] + [InlineData ("123456", 50, new [] { "123456" })] // No spaces + [InlineData ("1234567", 50, new [] { "1234567" })] // No spaces + [InlineData (" ", 50, new [] { " " })] // Just Spaces; should result in a single space + [InlineData (" ", 50, new [] { " " })] + [InlineData (" ", 50, new [] { " " })] + [InlineData ("12 456", 50, new [] { "12 456" })] // Single Spaces + [InlineData (" 2 456", 50, new [] { " 2 456" })] // Leading spaces should be preserved. + [InlineData (" 2 456 8", 50, new [] { " 2 456 8" })] + [InlineData ("A sentence has words. ", 50, new [] { "A sentence has words. " })] // Complex example + [InlineData ("12 567", 50, new [] { "12 567" })] // Double Spaces + [InlineData (" 3 567", 50, new [] { " 3 567" })] // Double Leading spaces should be preserved. + [InlineData (" 3 678 1", 50, new [] { " 3 678 1" })] + [InlineData ("1 456", 50, new [] { "1 456" })] + [InlineData ( + "A sentence has words. ", + 50, + new [] { "A sentence has words. " } + )] // Double space Complex example + public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_50 ( + string text, + int width, + IEnumerable resultLines + ) + { + List wrappedLines = TextFormatter.WordWrapText (text, width); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + + foreach (string line in wrappedLines) + { + breakLines += $"{line}{Environment.NewLine}"; + } + + var expected = string.Empty; + + foreach (string line in resultLines) + { + expected += $"{line}{Environment.NewLine}"; + } + + Assert.Equal (expected, breakLines); + } + + [Theory] + [InlineData ("A sentence has words.", 14, -7, new [] { "A sentence ", "has words." })] + [InlineData ("A sentence has words.", 8, -13, new [] { "A ", "sentence", " has ", "words." })] + [InlineData ("A sentence has words.", 6, -15, new [] { "A ", "senten", "ce ", "has ", "words." })] + [InlineData ("A sentence has words.", 3, -18, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds." })] + [InlineData ( + "A sentence has words.", + 2, + -19, + new [] { "A ", "se", "nt", "en", "ce", " ", "ha", "s ", "wo", "rd", "s." } + )] + [InlineData ( + "A sentence has words.", + 1, + -20, + new [] + { + "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." + } + )] + public void WordWrap_PreserveTrailingSpaces_True ( + string text, + int maxWidth, + int widthOffset, + IEnumerable resultLines + ) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + + [Theory] + [InlineData ("文に は言葉 があり ます。", 14, 0, new [] { "文に は言葉 ", "があり ます。" })] + [InlineData ("文に は言葉 があり ます。", 3, -11, new [] { "文", "に ", "は", "言", "葉 ", "が", "あ", "り ", "ま", "す", "。" })] + [InlineData ( + "文に は言葉 があり ます。", + 2, + -12, + new [] { "文", "に", " ", "は", "言", "葉", " ", "が", "あ", "り", " ", "ま", "す", "。" } + )] + [InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { })] + public void WordWrap_PreserveTrailingSpaces_True_Wide_Runes ( + string text, + int maxWidth, + int widthOffset, + IEnumerable resultLines + ) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + + [Theory] + [InlineData ("A sentence has words. ", 3, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds.", " " })] + [InlineData ( + "A sentence has words. ", + 3, + new [] { "A ", " ", "sen", "ten", "ce ", " ", " ", " ", "has", " ", "wor", "ds.", " " } + )] + public void WordWrap_PreserveTrailingSpaces_True_With_Simple_Runes_Width_3 ( + string text, + int width, + IEnumerable resultLines + ) + { + List wrappedLines = TextFormatter.WordWrapText (text, width, true); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + + foreach (string line in wrappedLines) + { + breakLines += $"{line}{Environment.NewLine}"; + } + + var expected = string.Empty; + + foreach (string line in resultLines) + { + expected += $"{line}{Environment.NewLine}"; + } + + Assert.Equal (expected, breakLines); + + // Double space Complex example - this is how VS 2022 does it + //text = "A sentence has words. "; + //breakLines = ""; + //wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true); + //foreach (var line in wrappedLines) { + // breakLines += $"{line}{Environment.NewLine}"; + //} + //expected = "A " + Environment.NewLine + + // " se" + Environment.NewLine + + // " nt" + Environment.NewLine + + // " en" + Environment.NewLine + + // " ce" + Environment.NewLine + + // " " + Environment.NewLine + + // " " + Environment.NewLine + + // " " + Environment.NewLine + + // " ha" + Environment.NewLine + + // " s " + Environment.NewLine + + // " wo" + Environment.NewLine + + // " rd" + Environment.NewLine + + // " s." + Environment.NewLine; + //Assert.Equal (expected, breakLines); + } + + [Theory] + [InlineData ("A sentence\t\t\t has words.", 14, -10, new [] { "A sentence\t", "\t\t has ", "words." })] + [InlineData ( + "A sentence\t\t\t has words.", + 8, + -16, + new [] { "A ", "sentence", "\t\t", "\t ", "has ", "words." } + )] + [InlineData ( + "A sentence\t\t\t has words.", + 3, + -21, + new [] { "A ", "sen", "ten", "ce", "\t", "\t", "\t", " ", "has", " ", "wor", "ds." } + )] + [InlineData ( + "A sentence\t\t\t has words.", + 2, + -22, + new [] { "A ", "se", "nt", "en", "ce", "\t", "\t", "\t", " ", "ha", "s ", "wo", "rd", "s." } + )] + [InlineData ( + "A sentence\t\t\t has words.", + 1, + -23, + new [] + { + "A", + " ", + "s", + "e", + "n", + "t", + "e", + "n", + "c", + "e", + "\t", + "\t", + "\t", + " ", + "h", + "a", + "s", + " ", + "w", + "o", + "r", + "d", + "s", + "." + } + )] + public void WordWrap_PreserveTrailingSpaces_True_With_Tab ( + string text, + int maxWidth, + int widthOffset, + IEnumerable resultLines, + int tabWidth = 4 + ) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true, tabWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + + [Theory] + [InlineData ("Constantinople", 14, 0, new [] { "Constantinople" })] + [InlineData ("Constantinople", 12, -2, new [] { "Constantinop", "le" })] + [InlineData ("Constantinople", 9, -5, new [] { "Constanti", "nople" })] + [InlineData ("Constantinople", 7, -7, new [] { "Constan", "tinople" })] + [InlineData ("Constantinople", 5, -9, new [] { "Const", "antin", "ople" })] + [InlineData ("Constantinople", 4, -10, new [] { "Cons", "tant", "inop", "le" })] + [InlineData ( + "Constantinople", + 1, + -13, + new [] { "C", "o", "n", "s", "t", "a", "n", "t", "i", "n", "o", "p", "l", "e" } + )] + public void WordWrap_SingleWordLine ( + string text, + int maxWidth, + int widthOffset, + IEnumerable resultLines + ) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + + [Theory] + [InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 20, 0, new [] { "This\u00A0is\u00A0a\u00A0sentence." })] + [InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 19, -1, new [] { "This\u00A0is\u00A0a\u00A0sentence." })] + [InlineData ( + "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence.", + 19, + 0, + new [] { "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence." } + )] + public void WordWrap_Unicode_2LinesWithNonBreakingSpace ( + string text, + int maxWidth, + int widthOffset, + IEnumerable resultLines + ) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + + [Theory] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 19, 0, new [] { "This\u00A0is\u00A0a\u00A0sentence." })] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 18, -1, new [] { "This\u00A0is\u00A0a\u00A0sentence", "." })] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 17, -2, new [] { "This\u00A0is\u00A0a\u00A0sentenc", "e." })] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 14, -5, new [] { "This\u00A0is\u00A0a\u00A0sent", "ence." })] + [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 10, -9, new [] { "This\u00A0is\u00A0a\u00A0", "sentence." })] + [InlineData ( + "This\u00A0is\u00A0a\u00A0sentence.", + 7, + -12, + new [] { "This\u00A0is", "\u00A0a\u00A0sent", "ence." } + )] + [InlineData ( + "This\u00A0is\u00A0a\u00A0sentence.", + 5, + -14, + new [] { "This\u00A0", "is\u00A0a\u00A0", "sente", "nce." } + )] + [InlineData ( + "This\u00A0is\u00A0a\u00A0sentence.", + 1, + -18, + new [] + { + "T", "h", "i", "s", "\u00A0", "i", "s", "\u00A0", "a", "\u00A0", "s", "e", "n", "t", "e", "n", "c", "e", "." + } + )] + public void WordWrap_Unicode_LineWithNonBreakingSpace ( + string text, + int maxWidth, + int widthOffset, + IEnumerable resultLines + ) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + + [Theory] + [InlineData ( + "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", + 51, + 0, + new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" } + )] + [InlineData ( + "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", + 50, + -1, + new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" } + )] + [InlineData ( + "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", + 46, + -5, + new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" } + )] + [InlineData ( + "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", + 26, + -25, + new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" } + )] + [InlineData ( + "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", + 17, + -34, + new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" } + )] + [InlineData ( + "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", + 13, + -38, + new [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" } + )] + [InlineData ( + "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", + 1, + -50, + new [] + { + "ก", + "ข", + "ฃ", + "ค", + "ฅ", + "ฆ", + "ง", + "จ", + "ฉ", + "ช", + "ซ", + "ฌ", + "ญ", + "ฎ", + "ฏ", + "ฐ", + "ฑ", + "ฒ", + "ณ", + "ด", + "ต", + "ถ", + "ท", + "ธ", + "น", + "บ", + "ป", + "ผ", + "ฝ", + "พ", + "ฟ", + "ภ", + "ม", + "ย", + "ร", + "ฤ", + "ล", + "ฦ", + "ว", + "ศ", + "ษ", + "ส", + "ห", + "ฬ", + "อ", + "ฮ", + "ฯ", + "ะั", + "า", + "ำ" + } + )] + public void WordWrap_Unicode_SingleWordLine ( + string text, + int maxWidth, + int widthOffset, + IEnumerable resultLines + ) + { + List wrappedLines; + + IEnumerable zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0); + Assert.Single (zeroWidth); + Assert.Equal ('ั', zeroWidth.ElementAt (0).Value); + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth + >= (wrappedLines.Count > 0 + ? wrappedLines.Max ( + l => l.GetRuneCount () + + zeroWidth.Count () + - 1 + + widthOffset + ) + : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + + /// WordWrap strips CRLF + [Theory] + [InlineData ( + "A sentence has words.\nA paragraph has lines.", + 44, + 0, + new [] { "A sentence has words.A paragraph has lines." } + )] + [InlineData ( + "A sentence has words.\nA paragraph has lines.", + 43, + -1, + new [] { "A sentence has words.A paragraph has lines." } + )] + [InlineData ( + "A sentence has words.\nA paragraph has lines.", + 38, + -6, + new [] { "A sentence has words.A paragraph has", "lines." } + )] + [InlineData ( + "A sentence has words.\nA paragraph has lines.", + 34, + -10, + new [] { "A sentence has words.A paragraph", "has lines." } + )] + [InlineData ( + "A sentence has words.\nA paragraph has lines.", + 27, + -17, + new [] { "A sentence has words.A", "paragraph has lines." } + )] + + // Unicode + [InlineData ( + "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", + 69, + 0, + new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." } + )] + [InlineData ( + "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", + 68, + -1, + new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." } + )] + [InlineData ( + "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", + 63, + -6, + new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has", "Линии." } + )] + [InlineData ( + "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", + 59, + -10, + new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт", "has Линии." } + )] + [InlineData ( + "A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", + 52, + -17, + new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode", "Пункт has Линии." } + )] + public void WordWrap_WithNewLines (string text, int maxWidth, int widthOffset, IEnumerable resultLines) + { + List wrappedLines; + + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + int expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + wrappedLines = TextFormatter.WordWrapText (text, maxWidth); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0) + ); + + Assert.True ( + expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0) + ); + Assert.Equal (resultLines, wrappedLines); + } + #region FormatAndGetSizeTests // TODO: Add multi-line examples @@ -7193,46 +7218,4 @@ B ")] } #endregion - - [Fact] - [SetupFakeDriver] - public void UICatalog_AboutBox_Text () - { - TextFormatter tf = new () - { - Text = UICatalog.UICatalogApp.GetAboutBoxMessage (), - Alignment = Alignment.Center, - VerticalAlignment = Alignment.Start, - WordWrap = false, - MultiLine = true, - HotKeySpecifier = (Rune)0xFFFF - }; - - Size tfSize = tf.FormatAndGetSize (); - Assert.Equal (new Size (58, 13), tfSize); - - ((FakeDriver)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height); - - Application.Driver.FillRect (Application.Screen, (Rune)'*'); - tf.Draw (Application.Screen, Attribute.Default, Attribute.Default); - - string expectedText = """ - ******UI Catalog: A comprehensive sample library for****** - ********************************************************** - _______ _ _ _____ _ - |__ __| (_) | | / ____| (_) - | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ - | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | - | | __/ | | | | | | | | | | | (_| | || |__| | |_| | | - |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| - ********************************************************** - **********************v2 - Pre-Alpha********************** - ********************************************************** - **********https://github.com/gui-cs/Terminal.Gui********** - ********************************************************** - """; - - TestHelpers.AssertDriverContentsAre (expectedText.ReplaceLineEndings (), _output); - - } } diff --git a/UnitTests/View/Layout/Dim.AutoTests.cs b/UnitTests/View/Layout/Dim.AutoTests.cs index bd0e2eaf7..5f6767310 100644 --- a/UnitTests/View/Layout/Dim.AutoTests.cs +++ b/UnitTests/View/Layout/Dim.AutoTests.cs @@ -4,33 +4,36 @@ using static Terminal.Gui.Dim; namespace Terminal.Gui.LayoutTests; +[Trait("Category", "Layout")] public partial class DimAutoTests (ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; - private class DimAutoTestView : View + [SetupFakeDriver] + [Fact] + public void Change_To_Non_Auto_Resets_ContentSize () { - public DimAutoTestView () + View view = new () { - ValidatePosDim = true; - Width = Auto (); - Height = Auto (); - } + Width = Auto (), + Height = Auto (), + Text = "01234" + }; + view.SetRelativeLayout (new (100, 100)); + Assert.Equal (new (0, 0, 5, 1), view.Frame); + Assert.Equal (new (5, 1), view.GetContentSize ()); - public DimAutoTestView (Dim width, Dim height) - { - ValidatePosDim = true; - Width = width; - Height = height; - } + // Change text to a longer string + view.Text = "0123456789"; - public DimAutoTestView (string text, Dim width, Dim height) - { - ValidatePosDim = true; - Text = text; - Width = width; - Height = height; - } + Assert.Equal (new (0, 0, 10, 1), view.Frame); + Assert.Equal (new (10, 1), view.GetContentSize ()); + + // If ContentSize was reset, these should cause it to update + view.Width = 5; + view.Height = 1; + + Assert.Equal (new (5, 1), view.GetContentSize ()); } [Theory] @@ -74,6 +77,46 @@ public partial class DimAutoTests (ITestOutputHelper output) Assert.Equal (new (0, 0, 10, expectedHeight), superView.Frame); } + [Theory] + [CombinatorialData] + public void HotKey_TextFormatter_Height_Correct ([CombinatorialValues ("1234", "_1234", "1_234", "____")] string text) + { + View view = new () + { + HotKeySpecifier = (Rune)'_', + Text = text, + Width = Auto (), + Height = 1 + }; + Assert.Equal (4, view.TextFormatter.ConstrainToWidth); + Assert.Equal (1, view.TextFormatter.ConstrainToHeight); + + view = new () + { + HotKeySpecifier = (Rune)'_', + TextDirection = TextDirection.TopBottom_LeftRight, + Text = text, + Width = 1, + Height = Auto () + }; + Assert.Equal (1, view.TextFormatter.ConstrainToWidth); + Assert.Equal (4, view.TextFormatter.ConstrainToHeight); + } + + [Theory] + [CombinatorialData] + public void HotKey_TextFormatter_Width_Correct ([CombinatorialValues ("1234", "_1234", "1_234", "____")] string text) + { + View view = new () + { + Text = text, + Height = 1, + Width = Auto () + }; + Assert.Equal (4, view.TextFormatter.ConstrainToWidth); + Assert.Equal (1, view.TextFormatter.ConstrainToHeight); + } + [Fact] public void NoSubViews_Does_Nothing () { @@ -158,6 +201,122 @@ public partial class DimAutoTests (ITestOutputHelper output) Assert.Equal (new (0, 0, expectedWidth, expectedHeight), superView.Frame); } + [Fact] + public void TestEquality () + { + var a = new DimAuto ( + MaximumContentDim: null, + MinimumContentDim: 1, + Style: DimAutoStyle.Auto + ); + + var b = new DimAuto ( + MaximumContentDim: null, + MinimumContentDim: 1, + Style: DimAutoStyle.Auto + ); + + var c = new DimAuto( + MaximumContentDim: 2, + MinimumContentDim: 1, + Style: DimAutoStyle.Auto + ); + + var d = new DimAuto ( + MaximumContentDim: null, + MinimumContentDim: 1, + Style: DimAutoStyle.Content + ); + + var e = new DimAuto ( + MaximumContentDim: null, + MinimumContentDim: 2, + Style: DimAutoStyle.Auto + ); + + // Test equality with same values + Assert.True (a.Equals (b)); + Assert.True (a.GetHashCode () == b.GetHashCode ()); + + // Test inequality with different MaximumContentDim + Assert.False (a.Equals (c)); + Assert.False (a.GetHashCode () == c.GetHashCode ()); + + // Test inequality with different Style + Assert.False (a.Equals (d)); + Assert.False (a.GetHashCode () == d.GetHashCode ()); + + // Test inequality with different MinimumContentDim + Assert.False (a.Equals (e)); + Assert.False (a.GetHashCode () == e.GetHashCode ()); + + // Test inequality with null + Assert.False (a.Equals (null)); + } + + [Fact] + public void TestEquality_Simple () + { + Dim a = Auto (); + Dim b = Auto (); + Assert.True (a.Equals (b)); + Assert.True (a.GetHashCode () == b.GetHashCode ()); + } + + [Fact] + public void TextFormatter_Settings_Change_View_Size () + { + View view = new () + { + Text = "_1234", + Width = Auto () + }; + Assert.Equal (new (4, 0), view.Frame.Size); + + view.Height = 1; + view.SetRelativeLayout (Application.Screen.Size); + Assert.Equal (new (4, 1), view.Frame.Size); + Size lastSize = view.Frame.Size; + + view.TextAlignment = Alignment.Fill; + Assert.Equal (lastSize, view.Frame.Size); + + view = new () + { + Text = "_1234", + Width = Auto (), + Height = 1 + }; + view.SetRelativeLayout (Application.Screen.Size); + + lastSize = view.Frame.Size; + view.VerticalTextAlignment = Alignment.Center; + Assert.Equal (lastSize, view.Frame.Size); + + view = new () + { + Text = "_1234", + Width = Auto (), + Height = 1 + }; + view.SetRelativeLayout (Application.Screen.Size); + lastSize = view.Frame.Size; + view.HotKeySpecifier = (Rune)'*'; + view.SetRelativeLayout (Application.Screen.Size); + Assert.NotEqual (lastSize, view.Frame.Size); + + view = new () + { + Text = "_1234", + Width = Auto (), + Height = 1 + }; + view.SetRelativeLayout (Application.Screen.Size); + lastSize = view.Frame.Size; + view.Text = "*ABCD"; + Assert.NotEqual (lastSize, view.Frame.Size); + } + // Test validation [Fact] public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims () @@ -410,46 +569,6 @@ public partial class DimAutoTests (ITestOutputHelper output) Assert.Equal (expectedWidth, superView.Frame.Width); } - [Theory] - [InlineData (0, 1, 1)] - [InlineData (1, 1, 1)] - [InlineData (9, 1, 1)] - [InlineData (10, 1, 1)] - [InlineData (0, 10, 10)] - [InlineData (1, 10, 10)] - [InlineData (9, 10, 10)] - [InlineData (10, 10, 10)] - public void Width_Auto_Text_Does_Not_Constrain_To_SuperView (int subX, int textLen, int expectedSubWidth) - { - var superView = new View - { - X = 0, - Y = 0, - Width = 10, - Height = 1, - ValidatePosDim = true - }; - - var subView = new View - { - Text = new ('*', textLen), - X = subX, - Y = 0, - Width = Auto (DimAutoStyle.Text), - Height = 1, - ValidatePosDim = true - }; - - superView.Add (subView); - - superView.BeginInit (); - superView.EndInit (); - superView.SetRelativeLayout (superView.GetContentSize ()); - - superView.LayoutSubviews (); - Assert.Equal (expectedSubWidth, subView.Frame.Width); - } - [Theory] [InlineData (0, 1, 1)] [InlineData (1, 1, 1)] @@ -499,31 +618,69 @@ public partial class DimAutoTests (ITestOutputHelper output) Assert.Equal (expectedSubWidth, subView.Frame.Width); } - [SetupFakeDriver] - [Fact] - public void Change_To_Non_Auto_Resets_ContentSize () + [Theory] + [InlineData (0, 1, 1)] + [InlineData (1, 1, 1)] + [InlineData (9, 1, 1)] + [InlineData (10, 1, 1)] + [InlineData (0, 10, 10)] + [InlineData (1, 10, 10)] + [InlineData (9, 10, 10)] + [InlineData (10, 10, 10)] + public void Width_Auto_Text_Does_Not_Constrain_To_SuperView (int subX, int textLen, int expectedSubWidth) { - View view = new () + var superView = new View { - Width = Auto (), - Height = Auto (), - Text = "01234" + X = 0, + Y = 0, + Width = 10, + Height = 1, + ValidatePosDim = true }; - view.SetRelativeLayout (new (100, 100)); - Assert.Equal (new (0, 0, 5, 1), view.Frame); - Assert.Equal (new (5, 1), view.GetContentSize ()); - // Change text to a longer string - view.Text = "0123456789"; + var subView = new View + { + Text = new ('*', textLen), + X = subX, + Y = 0, + Width = Auto (DimAutoStyle.Text), + Height = 1, + ValidatePosDim = true + }; - Assert.Equal (new (0, 0, 10, 1), view.Frame); - Assert.Equal (new (10, 1), view.GetContentSize ()); + superView.Add (subView); - // If ContentSize was reset, these should cause it to update - view.Width = 5; - view.Height = 1; + superView.BeginInit (); + superView.EndInit (); + superView.SetRelativeLayout (superView.GetContentSize ()); - Assert.Equal (new (5, 1), view.GetContentSize ()); + superView.LayoutSubviews (); + Assert.Equal (expectedSubWidth, subView.Frame.Width); + } + + private class DimAutoTestView : View + { + public DimAutoTestView () + { + ValidatePosDim = true; + Width = Auto (); + Height = Auto (); + } + + public DimAutoTestView (Dim width, Dim height) + { + ValidatePosDim = true; + Width = width; + Height = height; + } + + public DimAutoTestView (string text, Dim width, Dim height) + { + ValidatePosDim = true; + Text = text; + Width = width; + Height = height; + } } #region DimAutoStyle.Auto tests @@ -544,7 +701,6 @@ public partial class DimAutoTests (ITestOutputHelper output) view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (expectedW, expectedH), view.Frame.Size); - } [Fact] @@ -653,7 +809,6 @@ public partial class DimAutoTests (ITestOutputHelper output) view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (expectedW, expectedH), view.Frame.Size); - } [Theory] @@ -665,15 +820,14 @@ public partial class DimAutoTests (ITestOutputHelper output) public void DimAutoStyle_Text_Sizes_Correctly_With_Min (string text, int minWidth, int minHeight, int expectedW, int expectedH) { var view = new View (); - view.Width = Auto (DimAutoStyle.Text, minimumContentDim: minWidth); - view.Height = Auto (DimAutoStyle.Text, minimumContentDim: minHeight); + view.Width = Auto (DimAutoStyle.Text, minWidth); + view.Height = Auto (DimAutoStyle.Text, minHeight); view.Text = text; view.SetRelativeLayout (Application.Screen.Size); Assert.Equal (new (expectedW, expectedH), view.Frame.Size); - } [Theory] @@ -699,7 +853,6 @@ public partial class DimAutoTests (ITestOutputHelper output) [InlineData ("01234", 5, 1)] [InlineData ("01234ABCDE", 10, 1)] [InlineData ("01234\nABCDE", 5, 2)] - public void DimAutoStyle_Text_NoMin_Not_Constrained_By_ContentSize (string text, int expectedW, int expectedH) { var view = new View (); @@ -711,7 +864,6 @@ public partial class DimAutoTests (ITestOutputHelper output) Assert.Equal (new (expectedW, expectedH), view.Frame.Size); } - [Theory] [InlineData ("", 0, 0)] [InlineData (" ", 1, 1)] @@ -720,7 +872,7 @@ public partial class DimAutoTests (ITestOutputHelper output) [InlineData ("01234\nABCDE", 5, 2)] public void DimAutoStyle_Text_NoMin_Not_Constrained_By_SuperView (string text, int expectedW, int expectedH) { - var superView = new View () + var superView = new View { Width = 1, Height = 1 }; @@ -870,100 +1022,5 @@ public partial class DimAutoTests (ITestOutputHelper output) #endregion DimAutoStyle.Content tests - [Fact] - public void TextFormatter_Settings_Change_View_Size () - { - View view = new () - { - Text = "_1234", - Width = Auto () - }; - Assert.Equal (new (4, 0), view.Frame.Size); - - view.Height = 1; - view.SetRelativeLayout (Application.Screen.Size); - Assert.Equal (new (4, 1), view.Frame.Size); - Size lastSize = view.Frame.Size; - - view.TextAlignment = Alignment.Fill; - Assert.Equal (lastSize, view.Frame.Size); - - view = new () - { - Text = "_1234", - Width = Auto (), - Height = 1 - }; - view.SetRelativeLayout (Application.Screen.Size); - - lastSize = view.Frame.Size; - view.VerticalTextAlignment = Alignment.Center; - Assert.Equal (lastSize, view.Frame.Size); - - view = new () - { - Text = "_1234", - Width = Auto (), - Height = 1 - }; - view.SetRelativeLayout (Application.Screen.Size); - lastSize = view.Frame.Size; - view.HotKeySpecifier = (Rune)'*'; - view.SetRelativeLayout (Application.Screen.Size); - Assert.NotEqual (lastSize, view.Frame.Size); - - view = new () - { - Text = "_1234", - Width = Auto (), - Height = 1 - }; - view.SetRelativeLayout (Application.Screen.Size); - lastSize = view.Frame.Size; - view.Text = "*ABCD"; - Assert.NotEqual (lastSize, view.Frame.Size); - } - - - [Theory] - [CombinatorialData] - public void HotKey_TextFormatter_Width_Correct ([CombinatorialValues ("1234", "_1234", "1_234", "____")] string text) - { - View view = new () - { - Text = text, - Height = 1, - Width = Auto () - }; - Assert.Equal (4, view.TextFormatter.ConstrainToWidth); - Assert.Equal (1, view.TextFormatter.ConstrainToHeight); - } - - [Theory] - [CombinatorialData] - public void HotKey_TextFormatter_Height_Correct ([CombinatorialValues ("1234", "_1234", "1_234", "____")] string text) - { - View view = new () - { - HotKeySpecifier = (Rune)'_', - Text = text, - Width = Auto (), - Height = 1 - }; - Assert.Equal (4, view.TextFormatter.ConstrainToWidth); - Assert.Equal (1, view.TextFormatter.ConstrainToHeight); - - view = new () - { - HotKeySpecifier = (Rune)'_', - TextDirection = TextDirection.TopBottom_LeftRight, - Text = text, - Width = 1, - Height = Auto () - }; - Assert.Equal (1, view.TextFormatter.ConstrainToWidth); - Assert.Equal (4, view.TextFormatter.ConstrainToHeight); - } - // Test variations of Frame } diff --git a/UnitTests/View/Layout/Dim.FuncTests.cs b/UnitTests/View/Layout/Dim.FuncTests.cs index 12b9c562a..ab40cbf85 100644 --- a/UnitTests/View/Layout/Dim.FuncTests.cs +++ b/UnitTests/View/Layout/Dim.FuncTests.cs @@ -14,9 +14,12 @@ public class DimFuncTests (ITestOutputHelper output) Func f2 = () => 0; Dim dim1 = Func (f1); - Dim dim2 = Func (f2); + Dim dim2 = Func (f1); Assert.Equal (dim1, dim2); + dim2 = Func (f2); + Assert.NotEqual (dim1, dim2); + f2 = () => 1; dim2 = Func (f2); Assert.NotEqual (dim1, dim2); diff --git a/UnitTests/View/Layout/Dim.PercentTests.cs b/UnitTests/View/Layout/Dim.PercentTests.cs index adf23b46c..443e4e96f 100644 --- a/UnitTests/View/Layout/Dim.PercentTests.cs +++ b/UnitTests/View/Layout/Dim.PercentTests.cs @@ -15,7 +15,6 @@ public class DimPercentTests Assert.Equal (50, result); } - [Fact] public void DimPercent_Equals () { @@ -63,6 +62,15 @@ public class DimPercentTests Assert.NotEqual (dim1, dim2); } + [Fact] + public void TestEquality () + { + var a = Dim.Percent (32); + var b = Dim.Percent (32); + Assert.True (a.Equals (b)); + Assert.True (a.GetHashCode () == b.GetHashCode ()); + } + [Fact] public void DimPercent_Invalid_Throws () { @@ -157,7 +165,7 @@ public class DimPercentTests } [Theory] - [InlineData(0)] + [InlineData (0)] [InlineData (1)] [InlineData (50)] [InlineData (100)] diff --git a/UnitTests/View/Layout/Pos.AlignTests.cs b/UnitTests/View/Layout/Pos.AlignTests.cs index 216c7bdc6..05931e648 100644 --- a/UnitTests/View/Layout/Pos.AlignTests.cs +++ b/UnitTests/View/Layout/Pos.AlignTests.cs @@ -27,58 +27,58 @@ public class PosAlignTests Assert.Equal (0, posAlign.Aligner.ContainerSize); } - [Theory] - [InlineData (Alignment.Start, Alignment.Start, AlignmentModes.AddSpaceBetweenItems, AlignmentModes.AddSpaceBetweenItems, true)] - [InlineData (Alignment.Center, Alignment.Center, AlignmentModes.AddSpaceBetweenItems, AlignmentModes.AddSpaceBetweenItems, true)] - [InlineData (Alignment.Start, Alignment.Center, AlignmentModes.AddSpaceBetweenItems, AlignmentModes.AddSpaceBetweenItems, false)] - [InlineData (Alignment.Center, Alignment.Start, AlignmentModes.AddSpaceBetweenItems, AlignmentModes.AddSpaceBetweenItems, false)] - [InlineData (Alignment.Start, Alignment.Start, AlignmentModes.StartToEnd, AlignmentModes.AddSpaceBetweenItems, false)] - public void PosAlign_Equals (Alignment align1, Alignment align2, AlignmentModes mode1, AlignmentModes mode2, bool expectedEquals) - { - var posAlign1 = new PosAlign - { - Aligner = new() - { - Alignment = align1, - AlignmentModes = mode1 - } - }; + //[Theory] + //[InlineData (Alignment.Start, Alignment.Start, AlignmentModes.AddSpaceBetweenItems, AlignmentModes.AddSpaceBetweenItems, true)] + //[InlineData (Alignment.Center, Alignment.Center, AlignmentModes.AddSpaceBetweenItems, AlignmentModes.AddSpaceBetweenItems, true)] + //[InlineData (Alignment.Start, Alignment.Center, AlignmentModes.AddSpaceBetweenItems, AlignmentModes.AddSpaceBetweenItems, false)] + //[InlineData (Alignment.Center, Alignment.Start, AlignmentModes.AddSpaceBetweenItems, AlignmentModes.AddSpaceBetweenItems, false)] + //[InlineData (Alignment.Start, Alignment.Start, AlignmentModes.StartToEnd, AlignmentModes.AddSpaceBetweenItems, false)] + //public void PosAlign_Equals (Alignment align1, Alignment align2, AlignmentModes mode1, AlignmentModes mode2, bool expectedEquals) + //{ + // var posAlign1 = new PosAlign + // { + // Aligner = new () + // { + // Alignment = align1, + // AlignmentModes = mode1 + // } + // }; - var posAlign2 = new PosAlign - { - Aligner = new() - { - Alignment = align2, - AlignmentModes = mode2 - } - }; + // var posAlign2 = new PosAlign + // { + // Aligner = new () + // { + // Alignment = align2, + // AlignmentModes = mode2 + // } + // }; - Assert.Equal (expectedEquals, posAlign1.Equals (posAlign2)); - Assert.Equal (expectedEquals, posAlign2.Equals (posAlign1)); - } + // Assert.Equal (expectedEquals, posAlign1.Equals (posAlign2)); + // Assert.Equal (expectedEquals, posAlign2.Equals (posAlign1)); + //} - [Fact] - public void PosAlign_Equals_CachedLocation_Not_Used () - { - View superView = new () - { - Width = 10, - Height = 25 - }; - View view = new (); - superView.Add (view); + //[Fact] + //public void PosAlign_Equals_CachedLocation_Not_Used () + //{ + // View superView = new () + // { + // Width = 10, + // Height = 25 + // }; + // View view = new (); + // superView.Add (view); - Pos posAlign1 = Pos.Align (Alignment.Center); - view.X = posAlign1; - int pos1 = posAlign1.Calculate (10, Dim.Absolute (0)!, view, Dimension.Width); + // Pos posAlign1 = Pos.Align (Alignment.Center); + // view.X = posAlign1; + // int pos1 = posAlign1.Calculate (10, Dim.Absolute (0)!, view, Dimension.Width); - Pos posAlign2 = Pos.Align (Alignment.Center); - view.Y = posAlign2; - int pos2 = posAlign2.Calculate (25, Dim.Absolute (0)!, view, Dimension.Height); + // Pos posAlign2 = Pos.Align (Alignment.Center); + // view.Y = posAlign2; + // int pos2 = posAlign2.Calculate (25, Dim.Absolute (0)!, view, Dimension.Height); - Assert.NotEqual (pos1, pos2); - Assert.Equal (posAlign1, posAlign2); - } + // Assert.NotEqual (pos1, pos2); + // Assert.Equal (posAlign1, posAlign2); + //} [Fact] public void PosAlign_ToString () diff --git a/UnitTests/View/Layout/Pos.AnchorEndTests.cs b/UnitTests/View/Layout/Pos.AnchorEndTests.cs index 199e9e50e..b5dd8eb34 100644 --- a/UnitTests/View/Layout/Pos.AnchorEndTests.cs +++ b/UnitTests/View/Layout/Pos.AnchorEndTests.cs @@ -27,15 +27,6 @@ public class PosAnchorEndTests (ITestOutputHelper output) Assert.Equal (expectedEquals, posAnchorEnd2.Equals (posAnchorEnd1)); } - [Fact] - public void PosAnchorEnd_GetHashCode () - { - var posAnchorEnd = new PosAnchorEnd (10); - var expectedHashCode = 10.GetHashCode (); - - Assert.Equal (expectedHashCode, posAnchorEnd.GetHashCode ()); - } - [Fact] public void PosAnchorEnd_ToString () { diff --git a/UnitTests/View/Layout/Pos.CenterTests.cs b/UnitTests/View/Layout/Pos.CenterTests.cs index 7aa7a2ff9..ef7d5cc8b 100644 --- a/UnitTests/View/Layout/Pos.CenterTests.cs +++ b/UnitTests/View/Layout/Pos.CenterTests.cs @@ -15,16 +15,6 @@ public class PosCenterTests (ITestOutputHelper output) Assert.NotNull (posCenter); } - [Fact] - public void PosCenter_Equals () - { - var posCenter1 = new PosCenter (); - var posCenter2 = new PosCenter (); - - Assert.False (posCenter1.Equals (posCenter2)); - Assert.False (posCenter2.Equals (posCenter1)); - } - [Fact] public void PosCenter_ToString () { diff --git a/UnitTests/View/Layout/Pos.FuncTests.cs b/UnitTests/View/Layout/Pos.FuncTests.cs index 48b48a681..9874c9160 100644 --- a/UnitTests/View/Layout/Pos.FuncTests.cs +++ b/UnitTests/View/Layout/Pos.FuncTests.cs @@ -13,7 +13,7 @@ public class PosFuncTests (ITestOutputHelper output) Func f2 = () => 0; Pos pos1 = Pos.Func (f1); - Pos pos2 = Pos.Func (f2); + Pos pos2 = Pos.Func (f1); Assert.Equal (pos1, pos2); f2 = () => 1; diff --git a/UnitTests/View/Layout/Pos.Tests.cs b/UnitTests/View/Layout/Pos.Tests.cs index c3e6bb9a2..4b37d27ea 100644 --- a/UnitTests/View/Layout/Pos.Tests.cs +++ b/UnitTests/View/Layout/Pos.Tests.cs @@ -145,21 +145,6 @@ public class PosTests () ); } - [Fact] - public void PosFunction_Equal () - { - Func f1 = () => 0; - Func f2 = () => 0; - - Pos pos1 = Pos.Func (f1); - Pos pos2 = Pos.Func (f2); - Assert.Equal (pos1, pos2); - - f2 = () => 1; - pos2 = Pos.Func (f2); - Assert.NotEqual (pos1, pos2); - } - [Fact] public void PosFunction_SetsValue () { diff --git a/UnitTests/View/TitleTests.cs b/UnitTests/View/TitleTests.cs index 0c2a6a5b7..c808e66e4 100644 --- a/UnitTests/View/TitleTests.cs +++ b/UnitTests/View/TitleTests.cs @@ -3,7 +3,7 @@ using Xunit.Abstractions; namespace Terminal.Gui.ViewTests; -public class TitleTests (ITestOutputHelper output) +public class TitleTests { // Unit tests that verify look & feel of title are in BorderTests.cs diff --git a/UnitTests/Views/ContextMenuTests.cs b/UnitTests/Views/ContextMenuTests.cs index efeb709fb..99f9e3dfe 100644 --- a/UnitTests/Views/ContextMenuTests.cs +++ b/UnitTests/Views/ContextMenuTests.cs @@ -1359,4 +1359,53 @@ public class ContextMenuTests (ITestOutputHelper output) ) }; } + + [Fact] + [AutoInitShutdown] + public void Handling_TextField_With_Opened_ContextMenu_By_Mouse_HasFocus () + { + var tf1 = new TextField { Width = 10, Text = "TextField 1" }; + var tf2 = new TextField { Y = 2, Width = 10, Text = "TextField 2" }; + var win = new Window (); + win.Add (tf1, tf2); + var rs = Application.Begin (win); + + Assert.True (tf1.HasFocus); + Assert.False (tf2.HasFocus); + Assert.Equal (2, win.Subviews.Count); + Assert.Null (Application.MouseEnteredView); + + // Right click on tf2 to open context menu + Application.OnMouseEvent (new () { Position = new (1, 3), Flags = MouseFlags.Button3Clicked }); + Assert.False (tf1.HasFocus); + Assert.False (tf2.HasFocus); + Assert.Equal (3, win.Subviews.Count); + Assert.True (tf2.ContextMenu.MenuBar.IsMenuOpen); + Assert.True (win.Focused is Menu); + Assert.True (Application.MouseGrabView is MenuBar); + Assert.Equal (tf2, Application.MouseEnteredView); + + // Click on tf1 to focus it, which cause context menu being closed + Application.OnMouseEvent (new () { Position = new (1, 1), Flags = MouseFlags.Button1Clicked }); + Assert.True (tf1.HasFocus); + Assert.False (tf2.HasFocus); + Assert.Equal (2, win.Subviews.Count); + Assert.Null (tf2.ContextMenu.MenuBar); + Assert.Equal (win.Focused, tf1); + Assert.Null (Application.MouseGrabView); + Assert.Equal (tf1, Application.MouseEnteredView); + + // Click on tf2 to focus it + Application.OnMouseEvent (new () { Position = new (1, 3), Flags = MouseFlags.Button1Clicked }); + Assert.False (tf1.HasFocus); + Assert.True (tf2.HasFocus); + Assert.Equal (2, win.Subviews.Count); + Assert.Null (tf2.ContextMenu.MenuBar); + Assert.Equal (win.Focused, tf2); + Assert.Null (Application.MouseGrabView); + Assert.Equal (tf2, Application.MouseEnteredView); + + Application.End (rs); + win.Dispose (); + } } diff --git a/UnitTests/Views/ListViewTests.cs b/UnitTests/Views/ListViewTests.cs index 20180b45f..399dbf9c2 100644 --- a/UnitTests/Views/ListViewTests.cs +++ b/UnitTests/Views/ListViewTests.cs @@ -674,8 +674,10 @@ Item 6", private class NewListDataSource : IListDataSource { +#pragma warning disable CS0067 /// public event NotifyCollectionChangedEventHandler CollectionChanged; +#pragma warning restore CS0067 public int Count => 0; public int Length => 0; diff --git a/UnitTests/Views/MenuBarTests.cs b/UnitTests/Views/MenuBarTests.cs index ac8de4db0..9b1067290 100644 --- a/UnitTests/Views/MenuBarTests.cs +++ b/UnitTests/Views/MenuBarTests.cs @@ -3800,7 +3800,7 @@ Edit menuItem.RemoveMenuItem (); Assert.Single (menuBar.Menus); - Assert.Equal (null, menuBar.Menus [0].Children); + Assert.Null (menuBar.Menus [0].Children); Assert.Contains (Key.N.WithAlt, menuBar.KeyBindings.Bindings); Assert.DoesNotContain (Key.I, menuBar.KeyBindings.Bindings); diff --git a/UnitTests/Views/NumericUpDownTests.cs b/UnitTests/Views/NumericUpDownTests.cs index 9743de019..571a9fab4 100644 --- a/UnitTests/Views/NumericUpDownTests.cs +++ b/UnitTests/Views/NumericUpDownTests.cs @@ -3,7 +3,7 @@ using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; -public class NumericUpDownTests (ITestOutputHelper _output) +public class NumericUpDownTests { [Fact] public void WhenCreated_ShouldHaveDefaultValues_int () diff --git a/UnitTests/Views/StatusBarTests.cs b/UnitTests/Views/StatusBarTests.cs index 6f6f93061..14866a154 100644 --- a/UnitTests/Views/StatusBarTests.cs +++ b/UnitTests/Views/StatusBarTests.cs @@ -1,7 +1,7 @@ using Xunit.Abstractions; namespace Terminal.Gui.ViewsTests; -public class StatusBarTests (ITestOutputHelper output) +public class StatusBarTests { [Fact] public void AddItemAt_RemoveItem_Replacing () diff --git a/UnitTests/Views/TableViewTests.cs b/UnitTests/Views/TableViewTests.cs index 6db72eef7..0850689f6 100644 --- a/UnitTests/Views/TableViewTests.cs +++ b/UnitTests/Views/TableViewTests.cs @@ -2582,7 +2582,9 @@ A B C TestHelpers.AssertDriverContentsAre (expected, output); +#pragma warning disable xUnit2029 Assert.Empty (pets.Where (p => p.IsPicked)); +#pragma warning restore xUnit2029 tv.NewKeyDownEvent (Key.Space); @@ -2795,7 +2797,9 @@ A B C tv.NewKeyDownEvent (Key.Space); +#pragma warning disable xUnit2029 Assert.Empty (pets.Where (p => p.IsPicked)); +#pragma warning restore xUnit2029 tv.Draw (); @@ -2924,7 +2928,9 @@ A B C TestHelpers.AssertDriverContentsAre (expected, output); +#pragma warning disable xUnit2029 Assert.Empty (pets.Where (p => p.IsPicked)); +#pragma warning restore xUnit2029 tv.NewKeyDownEvent (Key.Space); @@ -3089,7 +3095,9 @@ A B C // Can untoggle at 1,0 even though 0,0 was initial toggle because FullRowSelect is on tableView.NewKeyDownEvent (new() { KeyCode = KeyCode.Space }); +#pragma warning disable xUnit2029 Assert.Empty (tableView.MultiSelectedRegions.Where (r => r.IsToggled)); +#pragma warning restore xUnit2029 } [Fact] diff --git a/UnitTests/Views/ToplevelTests.cs b/UnitTests/Views/ToplevelTests.cs index 98c0aa150..19b63c0a6 100644 --- a/UnitTests/Views/ToplevelTests.cs +++ b/UnitTests/Views/ToplevelTests.cs @@ -246,14 +246,26 @@ public partial class ToplevelTests (ITestOutputHelper output) top.OnUnloaded (); Assert.Equal ("Unloaded", eventInvoked); - top.AddMenuStatusBar (new MenuBar ()); + top.Add (new MenuBar ()); Assert.NotNull (top.MenuBar); - top.AddMenuStatusBar (new StatusBar ()); + top.Add (new StatusBar ()); Assert.NotNull (top.StatusBar); - top.RemoveMenuStatusBar (top.MenuBar); + var menuBar = top.MenuBar; + top.Remove (top.MenuBar); Assert.Null (top.MenuBar); - top.RemoveMenuStatusBar (top.StatusBar); + Assert.NotNull (menuBar); + var statusBar = top.StatusBar; + top.Remove (top.StatusBar); Assert.Null (top.StatusBar); + Assert.NotNull (statusBar); +#if true + Assert.False (menuBar.WasDisposed); + Assert.False (statusBar.WasDisposed); + menuBar.Dispose (); + statusBar.Dispose (); + Assert.True (menuBar.WasDisposed); + Assert.True (statusBar.WasDisposed); +#endif Application.Begin (top); Assert.Equal (top, Application.Top); @@ -265,7 +277,7 @@ public partial class ToplevelTests (ITestOutputHelper output) Assert.Equal (0, ny); Assert.Null (sb); - top.AddMenuStatusBar (new MenuBar ()); + top.Add (new MenuBar ()); Assert.NotNull (top.MenuBar); // Application.Top with a menu and without status bar. @@ -274,7 +286,7 @@ public partial class ToplevelTests (ITestOutputHelper output) Assert.Equal (1, ny); Assert.Null (sb); - top.AddMenuStatusBar (new StatusBar ()); + top.Add (new StatusBar ()); Assert.NotNull (top.StatusBar); // Application.Top with a menu and status bar. @@ -286,8 +298,10 @@ public partial class ToplevelTests (ITestOutputHelper output) Assert.Equal (2, ny); Assert.NotNull (sb); - top.RemoveMenuStatusBar (top.MenuBar); + menuBar = top.MenuBar; + top.Remove (top.MenuBar); Assert.Null (top.MenuBar); + Assert.NotNull (menuBar); // Application.Top without a menu and with a status bar. View.GetLocationEnsuringFullVisibility (top, 2, 2, out nx, out ny, out sb); @@ -298,8 +312,10 @@ public partial class ToplevelTests (ITestOutputHelper output) Assert.Equal (2, ny); Assert.NotNull (sb); - top.RemoveMenuStatusBar (top.StatusBar); + statusBar = top.StatusBar; + top.Remove (top.StatusBar); Assert.Null (top.StatusBar); + Assert.NotNull (statusBar); Assert.Null (top.MenuBar); var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () }; @@ -318,7 +334,7 @@ public partial class ToplevelTests (ITestOutputHelper output) Assert.Equal (0, ny); Assert.Null (sb); - top.AddMenuStatusBar (new MenuBar ()); + top.Add (new MenuBar ()); Assert.NotNull (top.MenuBar); // Application.Top with a menu and without status bar. @@ -327,7 +343,7 @@ public partial class ToplevelTests (ITestOutputHelper output) Assert.Equal (1, ny); Assert.Null (sb); - top.AddMenuStatusBar (new StatusBar ()); + top.Add (new StatusBar ()); Assert.NotNull (top.StatusBar); // Application.Top with a menu and status bar. @@ -339,10 +355,14 @@ public partial class ToplevelTests (ITestOutputHelper output) Assert.Equal (20, ny); Assert.NotNull (sb); - top.RemoveMenuStatusBar (top.MenuBar); - top.RemoveMenuStatusBar (top.StatusBar); - Assert.Null (top.StatusBar); + menuBar = top.MenuBar; + statusBar = top.StatusBar; + top.Remove (top.MenuBar); Assert.Null (top.MenuBar); + Assert.NotNull (menuBar); + top.Remove (top.StatusBar); + Assert.Null (top.StatusBar); + Assert.NotNull (statusBar); top.Remove (win); @@ -355,7 +375,7 @@ public partial class ToplevelTests (ITestOutputHelper output) Assert.Equal (0, ny); Assert.Null (sb); - top.AddMenuStatusBar (new MenuBar ()); + top.Add (new MenuBar ()); Assert.NotNull (top.MenuBar); // Application.Top with a menu and without status bar. @@ -364,7 +384,7 @@ public partial class ToplevelTests (ITestOutputHelper output) Assert.Equal (2, ny); Assert.Null (sb); - top.AddMenuStatusBar (new StatusBar ()); + top.Add (new StatusBar ()); Assert.NotNull (top.StatusBar); // Application.Top with a menu and status bar. @@ -387,7 +407,21 @@ public partial class ToplevelTests (ITestOutputHelper output) win.NewMouseEvent (new () { Position = new (6, 0), Flags = MouseFlags.Button1Pressed }); //Assert.Null (Toplevel._dragPosition); +#if true + Assert.False (top.MenuBar.WasDisposed); + Assert.False (top.StatusBar.WasDisposed); +#endif + menuBar = top.MenuBar; + statusBar = top.StatusBar; top.Dispose (); + Assert.Null (top.MenuBar); + Assert.Null (top.StatusBar); + Assert.NotNull (menuBar); + Assert.NotNull (statusBar); +#if true + Assert.True (menuBar.WasDisposed); + Assert.True (statusBar.WasDisposed); +#endif } [Fact (Skip = "#2491 - Test is broken until #2491 is more mature.")] @@ -1569,4 +1603,31 @@ public partial class ToplevelTests (ITestOutputHelper output) t.Dispose (); Application.Shutdown (); } + + [Fact] + public void Remove_Do_Not_Dispose_MenuBar_Or_StatusBar () + { + var mb = new MenuBar (); + var sb = new StatusBar (); + var tl = new Toplevel (); + +#if DEBUG + Assert.False (mb.WasDisposed); + Assert.False (sb.WasDisposed); +#endif + tl.Add (mb, sb); + Assert.NotNull (tl.MenuBar); + Assert.NotNull (tl.StatusBar); +#if DEBUG + Assert.False (mb.WasDisposed); + Assert.False (sb.WasDisposed); +#endif + tl.RemoveAll (); + Assert.Null (tl.MenuBar); + Assert.Null (tl.StatusBar); +#if DEBUG + Assert.False (mb.WasDisposed); + Assert.False (sb.WasDisposed); +#endif + } } diff --git a/docfx/docs/migratingfromv1.md b/docfx/docs/migratingfromv1.md index 0f6cd698d..feb73c967 100644 --- a/docfx/docs/migratingfromv1.md +++ b/docfx/docs/migratingfromv1.md @@ -167,6 +167,7 @@ The API for handling keyboard input is significantly improved. See [Keyboard API * The preferred way to enable Application-wide or View-heirarchy-dependent keystrokes is to use the [Shortcut](~/api/Terminal.Gui.Shortcut.yml) View or the built-in View's that utilize it, such as the [Bar](~/api/Terminal.Gui.Bar.yml)-based views. * The preferred way to handle single keystrokes is to use **Key Bindings**. Key Bindings map a key press to a [Command](~/api/Terminal.Gui.Command.yml). A view can declare which commands it supports, and provide a lambda that implements the functionality of the command, using `View.AddCommand()`. Use the [View.Keybindings](~/api/Terminal.Gui.View.Keybindings.yml) to configure the key bindings. * For better consistency and user experience, the default key for closing an app or `Toplevel` is now `Esc` (it was previously `Ctrl+Q`). +* The `Application.RootKeyEvent` method has been replaced with `Application.KeyDown` ### How to Fix @@ -176,6 +177,12 @@ The API for handling keyboard input is significantly improved. See [Keyboard API * It should be very uncommon for v2 code to override `OnKeyPressed` etc... * Anywhere `Ctrl+Q` was hard-coded as the "quit key", replace with `Application.QuitKey`. * See *Navigation* below for more information on v2's navigation keys. +* Replace `Application.RootKeyEvent` with `Application.KeyDown`. If the reason for subscribing to RootKeyEvent was to enable an application-wide action based on a key-press, consider using Application.KeyBindings instead. + +```diff +- Application.RootKeyEvent(KeyEvent arg) ++ Application.KeyDown(object? sender, Key e) +``` ## Updated Mouse API @@ -186,6 +193,7 @@ The API for mouse input is now internally consistent and easier to use. * Views can use the [View.Highlight](~/api/Terminal.Gui.View.Highlight.yml) event to have the view be visibly highlighted on various mouse events. * Views can set `View.WantContinousButtonPresses = true` to have their [Command.Accept](~/api/Terminal.Gui.Command.Accept.yml) command be invoked repeatedly as the user holds a mouse button down on the view. * Mouse and draw events now provide coordinates relative to the `Viewport` not the `Screen`. +* The `Application.RootMouseEvent` method has been replaced with `Application.MouseEvent` ### How to Fix @@ -193,6 +201,12 @@ The API for mouse input is now internally consistent and easier to use. * Use the [View.Highlight](~/api/Terminal.Gui.View.Highlight.yml) event to have the view be visibly highlighted on various mouse events. * Set `View.WantContinousButtonPresses = true` to have the [Command.Accept](~/api/Terminal.Gui.Command.Accept.yml) command be invoked repeatedly as the user holds a mouse button down on the view. * Update any code that assumed mouse events provided coordinates relative to the `Screen`. +* Replace `Application.RootMouseEvent` with `Application.MouseEvent`. + +```diff +- Application.RootMouseEvent(KeyEvent arg) ++ Application.MouseEvent(object? sender, MouseEvent mouseEvent) +``` ## Navigation - `Cursor`, `Focus`, `TabStop` etc... @@ -270,6 +284,21 @@ These keys are all registered as `KeyBindingScope.Application` key bindings by ` ... +## Button.Clicked Event Renamed + +The `Button.Clicked` event has been renamed `Button.Accept` + +## How to Fix + +Rename all instances of `Button.Clicked` to `Button.Accept`. Note the signature change to mouse events below. + +```diff +- btnLogin.Clicked ++ btnLogin.Accept +``` + +Alternatively, if you want to have key events as well as mouse events to fire an event, use `Button.Accept`. + ## Events now use `object sender, EventArgs args` signature Previously events in Terminal.Gui used a mixture of `Action` (no arguments), `Action` (or other raw datatype) and `Action`. Now all events use the `EventHandler` [standard .net design pattern](https://learn.microsoft.com/en-us/dotnet/csharp/event-pattern#event-delegate-signatures). @@ -302,8 +331,9 @@ If you previously had a lambda expression, you can simply add the extra argument ```diff - btnLogin.Clicked += () => { /*do something*/ }; -+ btnLogin.Clicked += (s,e) => { /*do something*/ }; ++ btnLogin.Accept += (s,e) => { /*do something*/ }; ``` +Note that the event name has also changed as noted above. If you have used a named method instead of a lamda you will need to update the signature e.g. @@ -391,4 +421,16 @@ Additionally, the `Toggle` event was renamed `CheckStateChanging` and made cance +} +preventChange = false; +cb.AdvanceCheckState (); -``` \ No newline at end of file +``` + +## `MainLoop` is no longer accessible from `Application` + +In v1, you could add timeouts via `Application.MainLoop.AddTimeout` among other things. In v2, the `MainLoop` object is internal to `Application` and methods previously accessed via `MainLoop` can now be accessed directly via `Application` + +### How to Fix + +```diff +- Application.MainLoop.AddTimeout (TimeSpan time, Func callback) ++ Application.AddTimeout (TimeSpan time, Func callback) +``` +