diff --git a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs index 9f8392d67..c20bed711 100644 --- a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs @@ -83,6 +83,7 @@ namespace Terminal.Gui { public override void GenerateSuggestions (AutocompleteContext context) { if (_suspendSuggestions) { + _suspendSuggestions = false; return; } base.GenerateSuggestions (context); diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 47390077e..eb93d90dd 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -1102,7 +1102,7 @@ namespace Terminal.Gui { _text = EnableNeedsFormat (value); if ((AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) || (textWasNull && Size.IsEmpty)) { - Size = CalcRect (0, 0, _text, _textDirection, TabWidth).Size; + Size = CalcRect (0, 0, _text, Direction, TabWidth).Size; } //if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) { @@ -1129,7 +1129,7 @@ namespace Terminal.Gui { set { _autoSize = EnableNeedsFormat (value); if (_autoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) { - Size = CalcRect (0, 0, Text, _textDirection, TabWidth).Size; + Size = CalcRect (0, 0, _text, Direction, TabWidth).Size; } } } @@ -1169,7 +1169,12 @@ namespace Terminal.Gui { /// The text vertical alignment. public TextDirection Direction { get => _textDirection; - set => _textDirection = EnableNeedsFormat (value); + set { + _textDirection = EnableNeedsFormat (value); + if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) { + Size = CalcRect (0, 0, Text, Direction, TabWidth).Size; + } + } } /// @@ -1250,7 +1255,7 @@ namespace Terminal.Gui { get => _size; set { if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) { - _size = EnableNeedsFormat (CalcRect (0, 0, Text, _textDirection, TabWidth).Size); + _size = EnableNeedsFormat (CalcRect (0, 0, Text, Direction, TabWidth).Size); } else { _size = EnableNeedsFormat (value); } @@ -1329,7 +1334,7 @@ namespace Terminal.Gui { shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos); } - if (IsVerticalDirection (_textDirection)) { + if (IsVerticalDirection (Direction)) { var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1, TabWidth); _lines = Format (shown_text, Size.Height, VerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth && WordWrap, PreserveTrailingSpaces, TabWidth, Direction, MultiLine); @@ -1434,7 +1439,7 @@ namespace Terminal.Gui { // Use "Lines" to ensure a Format (don't use "lines")) var linesFormated = Lines; - switch (_textDirection) { + switch (Direction) { case TextDirection.TopBottom_RightLeft: case TextDirection.LeftRight_BottomTop: case TextDirection.RightLeft_BottomTop: @@ -1443,7 +1448,7 @@ namespace Terminal.Gui { break; } - var isVertical = IsVerticalDirection (_textDirection); + var isVertical = IsVerticalDirection (Direction); var maxBounds = bounds; if (driver != null) { maxBounds = containerBounds == default @@ -1475,7 +1480,7 @@ namespace Terminal.Gui { var runes = _lines [line].ToRunes (); - switch (_textDirection) { + switch (Direction) { case TextDirection.RightLeft_BottomTop: case TextDirection.RightLeft_TopBottom: case TextDirection.BottomTop_LeftRight: @@ -1488,7 +1493,7 @@ namespace Terminal.Gui { int x, y; // Horizontal Alignment - if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (_textDirection))) { + if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (Direction))) { if (isVertical) { var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth); x = bounds.Right - runesWidth; @@ -1521,7 +1526,7 @@ namespace Terminal.Gui { } // Vertical Alignment - if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (_textDirection))) { + if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (Direction))) { if (isVertical) { y = bounds.Bottom - runes.Length; } else { diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 1d42fea10..21e4231ea 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -569,14 +569,14 @@ public partial class View { /// /// /// - /// Sets the . - /// - /// - /// Can be overridden if the view resize behavior is different than the default. + /// Determines the relative bounds of the and its s, and then calls + /// to update the view. /// /// - protected virtual void OnResizeNeeded () + internal void OnResizeNeeded () { + // TODO: Identify a real-world use-case where this API should be virtual. + // TODO: Until then leave it `internal` and non-virtual // First try SuperView.Bounds, then Application.Top, then Driver.Bounds. // Finally, if none of those are valid, use int.MaxValue (for Unit tests). var relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds : diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs index 94d8cad84..aa79c479c 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,9 +7,12 @@ namespace Terminal.Gui; public partial class View { ColorScheme _colorScheme; + // The view-relative region that needs to be redrawn. Marked internal for unit tests. + internal Rect _needsDisplayRect = Rect.Empty; + /// /// The color scheme for this view, if it is not defined, it returns the 's - /// color scheme. + /// color scheme. /// public virtual ColorScheme ColorScheme { get { @@ -27,12 +29,47 @@ public partial class View { } } + /// + /// Gets or sets whether the view needs to be redrawn. + /// + public bool NeedsDisplay { + get => _needsDisplayRect != Rect.Empty; + set { + if (value) { + SetNeedsDisplay (); + } else { + ClearNeedsDisplay (); + } + } + } + + /// + /// Gets whether any Subviews need to be redrawn. + /// + public bool SubViewNeedsDisplay { get; private set; } + + /// + /// The canvas that any line drawing that is to be shared by subviews of this view should add lines to. + /// + /// adds border lines to this LineCanvas. + public LineCanvas LineCanvas { get; } = new (); + + /// + /// Gets or sets whether this View will use it's SuperView's for + /// rendering any border lines. If the rendering of any borders drawn + /// by this Frame will be done by it's parent's SuperView. If (the default) + /// this View's method will be called to render the borders. + /// + public virtual bool SuperViewRendersLineCanvas { get; set; } = false; + /// /// Determines the current based on the value. /// - /// if is + /// + /// if is /// or if is . - /// If it's overridden can return other values. + /// If it's overridden can return other values. + /// public virtual Attribute GetNormalColor () { var cs = ColorScheme; @@ -45,17 +82,21 @@ public partial class View { /// /// Determines the current based on the value. /// - /// if is + /// + /// if is /// or if is . - /// If it's overridden can return other values. + /// If it's overridden can return other values. + /// public virtual Attribute GetFocusColor () => Enabled ? ColorScheme.Focus : ColorScheme.Disabled; /// /// Determines the current based on the value. /// - /// if is + /// + /// if is /// or if is . - /// If it's overridden can return other values. + /// If it's overridden can return other values. + /// public virtual Attribute GetHotNormalColor () => Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled; /// @@ -81,25 +122,8 @@ public partial class View { /// protected void ClearNeedsDisplay () { - _needsDisplayRect = Rect.Empty; - _subViewNeedsDisplay = false; - } - - // The view-relative region that needs to be redrawn. Marked internal for unit tests. - internal Rect _needsDisplayRect = Rect.Empty; - - /// - /// Gets or sets whether the view needs to be redrawn. - /// - public bool NeedsDisplay { - get => _needsDisplayRect != Rect.Empty; - set { - if (value) { - SetNeedsDisplay (); - } else { - ClearNeedsDisplay (); - } - } + _needsDisplayRect = Rect.Empty; + SubViewNeedsDisplay = false; } /// @@ -133,18 +157,18 @@ public partial class View { if (_needsDisplayRect.IsEmpty) { _needsDisplayRect = region; } else { - int x = Math.Min (_needsDisplayRect.X, region.X); - int y = Math.Min (_needsDisplayRect.Y, region.Y); - int w = Math.Max (_needsDisplayRect.Width, region.Width); - int h = Math.Max (_needsDisplayRect.Height, region.Height); + var x = Math.Min (_needsDisplayRect.X, region.X); + var y = Math.Min (_needsDisplayRect.Y, region.Y); + var w = Math.Max (_needsDisplayRect.Width, region.Width); + var h = Math.Max (_needsDisplayRect.Height, region.Height); _needsDisplayRect = new Rect (x, y, w, h); } _superView?.SetSubViewNeedsDisplay (); if (_needsDisplayRect.X < Bounds.X || - _needsDisplayRect.Y < Bounds.Y || - _needsDisplayRect.Width > Bounds.Width || - _needsDisplayRect.Height > Bounds.Height) { + _needsDisplayRect.Y < Bounds.Y || + _needsDisplayRect.Width > Bounds.Width || + _needsDisplayRect.Height > Bounds.Height) { Margin?.SetNeedsDisplay (Margin.Bounds); Border?.SetNeedsDisplay (Border.Bounds); Padding?.SetNeedsDisplay (Padding.Bounds); @@ -164,31 +188,24 @@ public partial class View { } } - /// - /// Gets whether any Subviews need to be redrawn. - /// - public bool SubViewNeedsDisplay => _subViewNeedsDisplay; - - bool _subViewNeedsDisplay; - /// /// Indicates that any Subviews (in the list) need to be repainted. /// public void SetSubViewNeedsDisplay () { - _subViewNeedsDisplay = true; - if (_superView != null && !_superView._subViewNeedsDisplay) { + SubViewNeedsDisplay = true; + if (_superView != null && !_superView.SubViewNeedsDisplay) { _superView.SetSubViewNeedsDisplay (); } } /// - /// Clears the with the normal background color. + /// Clears the with the normal background color. /// /// - /// - /// This clears the Bounds used by this view. - /// + /// + /// This clears the Bounds used by this view. + /// /// public void Clear () { @@ -202,7 +219,7 @@ public partial class View { // "View APIs only deal with View-relative coords". This is only used by ComboBox which can // be refactored to use the View-relative version. /// - /// Clears the specified screen-relative rectangle with the normal background. + /// Clears the specified screen-relative rectangle with the normal background. /// /// /// @@ -220,10 +237,10 @@ public partial class View { // Clips a rectangle in screen coordinates to the dimensions currently available on the screen internal Rect ScreenClip (Rect regionScreen) { - int x = regionScreen.X < 0 ? 0 : regionScreen.X; - int y = regionScreen.Y < 0 ? 0 : regionScreen.Y; - int w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width; - int h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height; + var x = regionScreen.X < 0 ? 0 : regionScreen.X; + var y = regionScreen.Y < 0 ? 0 : regionScreen.Y; + var w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width; + var h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height; return new Rect (x, y, w, h); } @@ -231,11 +248,15 @@ public partial class View { /// /// Expands the 's clip region to include . /// - /// The current screen-relative clip region, which can be then re-applied by setting . + /// + /// The current screen-relative clip region, which can be then re-applied by setting + /// . + /// /// - /// - /// If and do not intersect, the clip region will be set to . - /// + /// + /// If and do not intersect, the clip region will be set to + /// . + /// /// public Rect ClipToBounds () { @@ -251,14 +272,17 @@ public partial class View { /// Hot color. /// Normal color. /// - /// The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by default. - /// The hotkey specifier can be changed via + /// + /// The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by + /// default. + /// + /// The hotkey specifier can be changed via /// public void DrawHotString (string text, Attribute hotColor, Attribute normalColor) { var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier; Application.Driver.SetAttribute (normalColor); - foreach (char rune in text) { + foreach (var rune in text) { if (rune == hotkeySpec.Value) { Application.Driver.SetAttribute (hotColor); continue; @@ -272,7 +296,10 @@ public partial class View { /// Utility function to draw strings that contains a hotkey using a and the "focused" state. /// /// String to display, the underscore before a letter flags the next letter as the hotkey. - /// If set to this uses the focused colors from the color scheme, otherwise the regular ones. + /// + /// If set to this uses the focused colors from the color scheme, otherwise + /// the regular ones. + /// /// The color scheme to use. public void DrawHotString (string text, bool focused, ColorScheme scheme) { @@ -295,28 +322,15 @@ public partial class View { return; } - BoundsToScreen (col, row, out int rCol, out int rRow, false); + BoundsToScreen (col, row, out var rCol, out var rRow, false); Driver?.Move (rCol, rRow); } - /// - /// The canvas that any line drawing that is to be shared by subviews of this view should add lines to. - /// - /// adds border lines to this LineCanvas. - public LineCanvas LineCanvas { get; } = new (); - - /// - /// Gets or sets whether this View will use it's SuperView's for - /// rendering any border lines. If the rendering of any borders drawn - /// by this Frame will be done by it's parent's SuperView. If (the default) - /// this View's method will be called to render the borders. - /// - public virtual bool SuperViewRendersLineCanvas { get; set; } = false; - // TODO: Make this cancelable /// - /// Prepares . If is true, only the of - /// this view's subviews will be rendered. If is false (the default), this + /// Prepares . If is true, only the + /// of + /// this view's subviews will be rendered. If is false (the default), this /// method will cause the be prepared to be rendered. /// /// @@ -336,21 +350,22 @@ public partial class View { } /// - /// Draws the view. Causes the following virtual methods to be called (along with their related events): + /// Draws the view. Causes the following virtual methods to be called (along with their related events): /// , . /// /// - /// - /// Always use (view-relative) when calling , NOT (superview-relative). - /// - /// - /// Views should set the color that they want to use on entry, as otherwise this will inherit - /// the last color that was set globally on the driver. - /// - /// - /// Overrides of must ensure they do not set Driver.Clip to a clip region - /// larger than the property, as this will cause the driver to clip the entire region. - /// + /// + /// Always use (view-relative) when calling , NOT + /// (superview-relative). + /// + /// + /// Views should set the color that they want to use on entry, as otherwise this will inherit + /// the last color that was set globally on the driver. + /// + /// + /// Overrides of must ensure they do not set Driver.Clip to a clip region + /// larger than the property, as this will cause the driver to clip the entire region. + /// /// public void Draw () { @@ -387,8 +402,9 @@ public partial class View { // TODO: Make this cancelable /// - /// Renders . If is true, only the of - /// this view's subviews will be rendered. If is false (the default), this + /// Renders . If is true, only the + /// of + /// this view's subviews will be rendered. If is false (the default), this /// method will cause the to be rendered. /// /// @@ -411,7 +427,7 @@ public partial class View { } if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) { - foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) { + foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas)) { // Combine the LineCanvas' LineCanvas.Merge (subview.LineCanvas); subview.LineCanvas.Clear (); @@ -434,21 +450,25 @@ public partial class View { /// Event invoked when the content area of the View is to be drawn. /// /// - /// - /// Will be invoked before any subviews added with have been drawn. - /// - /// - /// Rect provides the view-relative rectangle describing the currently visible viewport into the . - /// + /// + /// Will be invoked before any subviews added with have been drawn. + /// + /// + /// Rect provides the view-relative rectangle describing the currently visible viewport into the + /// . + /// /// public event EventHandler DrawContent; /// - /// Enables overrides to draw infinitely scrolled content and/or a background behind added controls. + /// Enables overrides to draw infinitely scrolled content and/or a background behind added controls. /// - /// The view-relative rectangle describing the currently visible viewport into the + /// + /// The view-relative rectangle describing the currently visible viewport into the + /// + /// /// - /// This method will be called before any subviews added with have been drawn. + /// This method will be called before any subviews added with have been drawn. /// public virtual void OnDrawContent (Rect contentArea) { @@ -475,8 +495,8 @@ public partial class View { var subviewsNeedingDraw = _subviews.Where ( view => view.Visible && (view.NeedsDisplay || - view.SubViewNeedsDisplay || - view.LayoutNeeded) + view.SubViewNeedsDisplay || + view.LayoutNeeded) ); foreach (var view in subviewsNeedingDraw) { @@ -499,19 +519,23 @@ public partial class View { /// Event invoked when the content area of the View is completed drawing. /// /// - /// - /// Will be invoked after any subviews removed with have been completed drawing. - /// - /// - /// Rect provides the view-relative rectangle describing the currently visible viewport into the . - /// + /// + /// Will be invoked after any subviews removed with have been completed drawing. + /// + /// + /// Rect provides the view-relative rectangle describing the currently visible viewport into the + /// . + /// /// public event EventHandler DrawContentComplete; /// /// Enables overrides after completed drawing infinitely scrolled content and/or a background behind removed controls. /// - /// The view-relative rectangle describing the currently visible viewport into the + /// + /// The view-relative rectangle describing the currently visible viewport into the + /// + /// /// /// This method will be called after any subviews removed with have been completed drawing. /// diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 36eecb52e..6c67882d8 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -1,724 +1,725 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -namespace Terminal.Gui { - public partial class View { - static readonly IList _empty = new List (0).AsReadOnly (); +namespace Terminal.Gui; - View _superView = null; +public partial class View { + static readonly IList _empty = new List (0).AsReadOnly (); - /// - /// Returns the container for this view, or null if this view has not been added to a container. - /// - /// The super view. - public virtual View SuperView { - get { - return _superView; + internal bool _addingView; + + List _subviews; // This is null, and allocated on demand. + + View _superView; + + /// + /// Returns the container for this view, or null if this view has not been added to a container. + /// + /// The super view. + public virtual View SuperView { + get => _superView; + set => throw new NotImplementedException (); + } + + /// + /// This returns a list of the subviews contained by this view. + /// + /// The subviews. + public IList Subviews => _subviews?.AsReadOnly () ?? _empty; + + // Internally, we use InternalSubviews rather than subviews, as we do not expect us + // to make the same mistakes our users make when they poke at the Subviews. + internal IList InternalSubviews => _subviews ?? _empty; + + /// + /// Returns a value indicating if this View is currently on Top (Active) + /// + public bool IsCurrentTop => Application.Current == this; + + /// + /// Indicates whether the view was added to . + /// + public bool IsAdded { get; private set; } + + /// + /// Event fired when this view is added to another. + /// + public event EventHandler Added; + + /// + /// Adds a subview (child) to this view. + /// + /// + /// The Views that have been added to this view can be retrieved via the property. + /// See also + /// + public virtual void Add (View view) + { + if (view == null) { + return; + } + if (_subviews == null) { + _subviews = new List (); + } + if (_tabIndexes == null) { + _tabIndexes = new List (); + } + _subviews.Add (view); + _tabIndexes.Add (view); + view._superView = this; + if (view.CanFocus) { + _addingView = true; + if (SuperView?.CanFocus == false) { + SuperView._addingView = true; + SuperView.CanFocus = true; + SuperView._addingView = false; } - set { - throw new NotImplementedException (); + CanFocus = true; + view._tabIndex = _tabIndexes.IndexOf (view); + _addingView = false; + } + if (view.Enabled && !Enabled) { + view._oldEnabled = true; + view.Enabled = false; + } + + OnAdded (new SuperViewChangedEventArgs (this, view)); + if (IsInitialized && !view.IsInitialized) { + view.BeginInit (); + view.EndInit (); + } + + SetNeedsLayout (); + SetNeedsDisplay (); + } + + /// + /// Adds the specified views (children) to the view. + /// + /// Array of one or more views (can be optional parameter). + /// + /// The Views that have been added to this view can be retrieved via the property. + /// See also + /// + public void Add (params View [] views) + { + if (views == null) { + return; + } + foreach (var view in views) { + Add (view); + } + } + + /// + /// Method invoked when a subview is being added to this view. + /// + /// Event where is the subview being added. + public virtual void OnAdded (SuperViewChangedEventArgs e) + { + var view = e.Child; + view.IsAdded = true; + view.OnResizeNeeded (); + view.Added?.Invoke (this, e); + } + + /// + /// Event fired when this view is removed from another. + /// + public event EventHandler Removed; + + /// + /// Removes all subviews (children) added via or from this View. + /// + public virtual void RemoveAll () + { + if (_subviews == null) { + return; + } + + while (_subviews.Count > 0) { + Remove (_subviews [0]); + } + } + + /// + /// Removes a subview added via or from this View. + /// + /// + /// + public virtual void Remove (View view) + { + if (view == null || _subviews == null) { + return; + } + + var touched = view.Frame; + _subviews.Remove (view); + _tabIndexes.Remove (view); + view._superView = null; + view._tabIndex = -1; + SetNeedsLayout (); + SetNeedsDisplay (); + + foreach (var v in _subviews) { + if (v.Frame.IntersectsWith (touched)) { + view.SetNeedsDisplay (); + } + } + OnRemoved (new SuperViewChangedEventArgs (this, view)); + if (Focused == view) { + Focused = null; + } + } + + /// + /// Method invoked when a subview is being removed from this view. + /// + /// Event args describing the subview being removed. + public virtual void OnRemoved (SuperViewChangedEventArgs e) + { + var view = e.Child; + view.IsAdded = false; + view.Removed?.Invoke (this, e); + } + + + void PerformActionForSubview (View subview, Action action) + { + if (_subviews.Contains (subview)) { + action (subview); + } + + SetNeedsDisplay (); + subview.SetNeedsDisplay (); + } + + /// + /// Brings the specified subview to the front so it is drawn on top of any other views. + /// + /// The subview to send to the front + /// + /// . + /// + public void BringSubviewToFront (View subview) => PerformActionForSubview (subview, x => { + _subviews.Remove (x); + _subviews.Add (x); + }); + + /// + /// Sends the specified subview to the front so it is the first view drawn + /// + /// The subview to send to the front + /// + /// . + /// + public void SendSubviewToBack (View subview) => PerformActionForSubview (subview, x => { + _subviews.Remove (x); + _subviews.Insert (0, subview); + }); + + /// + /// Moves the subview backwards in the hierarchy, only one step + /// + /// The subview to send backwards + /// + /// If you want to send the view all the way to the back use SendSubviewToBack. + /// + public void SendSubviewBackwards (View subview) => PerformActionForSubview (subview, x => { + var idx = _subviews.IndexOf (x); + if (idx > 0) { + _subviews.Remove (x); + _subviews.Insert (idx - 1, x); + } + }); + + /// + /// Moves the subview backwards in the hierarchy, only one step + /// + /// The subview to send backwards + /// + /// If you want to send the view all the way to the back use SendSubviewToBack. + /// + public void BringSubviewForward (View subview) => PerformActionForSubview (subview, x => { + var idx = _subviews.IndexOf (x); + if (idx + 1 < _subviews.Count) { + _subviews.Remove (x); + _subviews.Insert (idx + 1, x); + } + }); + + /// + /// Get the top superview of a given . + /// + /// The superview view. + public View GetTopSuperView (View view = null, View superview = null) + { + var top = superview ?? Application.Top; + for (var v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) { + top = v; + if (top == superview) { + break; } } - List _subviews; // This is null, and allocated on demand. - /// - /// This returns a list of the subviews contained by this view. - /// - /// The subviews. - public IList Subviews => _subviews?.AsReadOnly () ?? _empty; + return top; + } - // Internally, we use InternalSubviews rather than subviews, as we do not expect us - // to make the same mistakes our users make when they poke at the Subviews. - internal IList InternalSubviews => _subviews ?? _empty; - /// - /// Returns a value indicating if this View is currently on Top (Active) - /// - public bool IsCurrentTop => Application.Current == this; - /// - /// Event fired when this view is added to another. - /// - public event EventHandler Added; + #region Focus + internal enum Direction { + Forward, + Backward + } - internal bool _addingView; + /// + /// Event fired when the view gets focus. + /// + public event EventHandler Enter; - /// - /// Adds a subview (child) to this view. - /// - /// - /// The Views that have been added to this view can be retrieved via the property. - /// See also - /// - public virtual void Add (View view) - { - if (view == null) { - return; + /// + /// Event fired when the view looses focus. + /// + public event EventHandler Leave; + + Direction _focusDirection; + + internal Direction FocusDirection { + get => SuperView?.FocusDirection ?? _focusDirection; + set { + if (SuperView != null) { + SuperView.FocusDirection = value; + } else { + _focusDirection = value; } - if (_subviews == null) { - _subviews = new List (); + } + } + + + // BUGBUG: v2 - Seems weird that this is in View and not Responder. + bool _hasFocus; + + /// + public override bool HasFocus => _hasFocus; + + void SetHasFocus (bool value, View view, bool force = false) + { + if (_hasFocus != value || force) { + _hasFocus = value; + if (value) { + OnEnter (view); + } else { + OnLeave (view); } - if (_tabIndexes == null) { - _tabIndexes = new List (); + SetNeedsDisplay (); + } + + // Remove focus down the chain of subviews if focus is removed + if (!value && Focused != null) { + var f = Focused; + f.OnLeave (view); + f.SetHasFocus (false, view); + Focused = null; + } + } + + /// + /// Event fired when the value is being changed. + /// + public event EventHandler CanFocusChanged; + + /// + public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty); + + bool _oldCanFocus; + + /// + public override bool CanFocus { + get => base.CanFocus; + set { + if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) { + throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); } - _subviews.Add (view); - _tabIndexes.Add (view); - view._superView = this; - if (view.CanFocus) { - _addingView = true; - if (SuperView?.CanFocus == false) { - SuperView._addingView = true; + if (base.CanFocus != value) { + base.CanFocus = value; + + switch (value) { + case false when _tabIndex > -1: + TabIndex = -1; + break; + case true when SuperView?.CanFocus == false && _addingView: SuperView.CanFocus = true; - SuperView._addingView = false; - } - CanFocus = true; - view._tabIndex = _tabIndexes.IndexOf (view); - _addingView = false; - } - if (view.Enabled && !Enabled) { - view._oldEnabled = true; - view.Enabled = false; - } - - OnAdded (new SuperViewChangedEventArgs (this, view)); - if (IsInitialized && !view.IsInitialized) { - view.BeginInit (); - view.EndInit (); - } - - SetNeedsLayout (); - SetNeedsDisplay (); - } - - /// - /// Adds the specified views (children) to the view. - /// - /// Array of one or more views (can be optional parameter). - /// - /// The Views that have been added to this view can be retrieved via the property. - /// See also - /// - public void Add (params View [] views) - { - if (views == null) { - return; - } - foreach (var view in views) { - Add (view); - } - } - - /// - /// Method invoked when a subview is being added to this view. - /// - /// Event where is the subview being added. - public virtual void OnAdded (SuperViewChangedEventArgs e) - { - var view = e.Child; - view.IsAdded = true; - view.OnResizeNeeded (); - view.Added?.Invoke (this, e); - } - - /// - /// Indicates whether the view was added to . - /// - public bool IsAdded { get; private set; } - - /// - /// Event fired when this view is removed from another. - /// - public event EventHandler Removed; - - /// - /// Removes all subviews (children) added via or from this View. - /// - public virtual void RemoveAll () - { - if (_subviews == null) { - return; - } - - while (_subviews.Count > 0) { - Remove (_subviews [0]); - } - } - - /// - /// Removes a subview added via or from this View. - /// - /// - /// - public virtual void Remove (View view) - { - if (view == null || _subviews == null) return; - - var touched = view.Frame; - _subviews.Remove (view); - _tabIndexes.Remove (view); - view._superView = null; - view._tabIndex = -1; - SetNeedsLayout (); - SetNeedsDisplay (); - - foreach (var v in _subviews) { - if (v.Frame.IntersectsWith (touched)) - view.SetNeedsDisplay (); - } - OnRemoved (new SuperViewChangedEventArgs (this, view)); - if (_focused == view) { - _focused = null; - } - } - - /// - /// Method invoked when a subview is being removed from this view. - /// - /// Event args describing the subview being removed. - public virtual void OnRemoved (SuperViewChangedEventArgs e) - { - var view = e.Child; - view.IsAdded = false; - view.Removed?.Invoke (this, e); - } - - - void PerformActionForSubview (View subview, Action action) - { - if (_subviews.Contains (subview)) { - action (subview); - } - - SetNeedsDisplay (); - subview.SetNeedsDisplay (); - } - - /// - /// Brings the specified subview to the front so it is drawn on top of any other views. - /// - /// The subview to send to the front - /// - /// . - /// - public void BringSubviewToFront (View subview) - { - PerformActionForSubview (subview, x => { - _subviews.Remove (x); - _subviews.Add (x); - }); - } - - /// - /// Sends the specified subview to the front so it is the first view drawn - /// - /// The subview to send to the front - /// - /// . - /// - public void SendSubviewToBack (View subview) - { - PerformActionForSubview (subview, x => { - _subviews.Remove (x); - _subviews.Insert (0, subview); - }); - } - - /// - /// Moves the subview backwards in the hierarchy, only one step - /// - /// The subview to send backwards - /// - /// If you want to send the view all the way to the back use SendSubviewToBack. - /// - public void SendSubviewBackwards (View subview) - { - PerformActionForSubview (subview, x => { - var idx = _subviews.IndexOf (x); - if (idx > 0) { - _subviews.Remove (x); - _subviews.Insert (idx - 1, x); - } - }); - } - - /// - /// Moves the subview backwards in the hierarchy, only one step - /// - /// The subview to send backwards - /// - /// If you want to send the view all the way to the back use SendSubviewToBack. - /// - public void BringSubviewForward (View subview) - { - PerformActionForSubview (subview, x => { - var idx = _subviews.IndexOf (x); - if (idx + 1 < _subviews.Count) { - _subviews.Remove (x); - _subviews.Insert (idx + 1, x); - } - }); - } - - /// - /// Get the top superview of a given . - /// - /// The superview view. - public View GetTopSuperView (View view = null, View superview = null) - { - View top = superview ?? Application.Top; - for (var v = view?.SuperView ?? (this?.SuperView); v != null; v = v.SuperView) { - top = v; - if (top == superview) { break; } - } - return top; - } - - - - #region Focus - View _focused = null; - - internal enum Direction { - Forward, - Backward - } - - /// - /// Event fired when the view gets focus. - /// - public event EventHandler Enter; - - /// - /// Event fired when the view looses focus. - /// - public event EventHandler Leave; - - Direction _focusDirection; - internal Direction FocusDirection { - get => SuperView?.FocusDirection ?? _focusDirection; - set { - if (SuperView != null) - SuperView.FocusDirection = value; - else - _focusDirection = value; - } - } - - - // BUGBUG: v2 - Seems weird that this is in View and not Responder. - bool _hasFocus; - - /// - public override bool HasFocus => _hasFocus; - - void SetHasFocus (bool value, View view, bool force = false) - { - if (_hasFocus != value || force) { - _hasFocus = value; - if (value) { - OnEnter (view); - } else { - OnLeave (view); + if (value && _tabIndex == -1) { + TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1; } + TabStop = value; + + if (!value && SuperView?.Focused == this) { + SuperView.Focused = null; + } + if (!value && HasFocus) { + SetHasFocus (false, this); + SuperView?.EnsureFocus (); + if (SuperView != null && SuperView.Focused == null) { + SuperView.FocusNext (); + if (SuperView.Focused == null && Application.Current != null) { + Application.Current.FocusNext (); + } + Application.BringOverlappedTopToFront (); + } + } + if (_subviews != null && IsInitialized) { + foreach (var view in _subviews) { + if (view.CanFocus != value) { + if (!value) { + view._oldCanFocus = view.CanFocus; + view._oldTabIndex = view._tabIndex; + view.CanFocus = false; + view._tabIndex = -1; + } else { + if (_addingView) { + view._addingView = true; + } + view.CanFocus = view._oldCanFocus; + view._tabIndex = view._oldTabIndex; + view._addingView = false; + } + } + } + } + OnCanFocusChanged (); SetNeedsDisplay (); } + } + } - // Remove focus down the chain of subviews if focus is removed - if (!value && _focused != null) { - var f = _focused; - f.OnLeave (view); - f.SetHasFocus (false, view); - _focused = null; - } + + /// + public override bool OnEnter (View view) + { + var args = new FocusEventArgs (view); + Enter?.Invoke (this, args); + if (args.Handled) { + return true; + } + if (base.OnEnter (view)) { + return true; } - /// - /// Event fired when the value is being changed. - /// - public event EventHandler CanFocusChanged; + return false; + } - /// - public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty); - - bool _oldCanFocus; - /// - public override bool CanFocus { - get => base.CanFocus; - set { - if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) { - throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!"); - } - if (base.CanFocus != value) { - base.CanFocus = value; - - switch (value) { - case false when _tabIndex > -1: - TabIndex = -1; - break; - case true when SuperView?.CanFocus == false && _addingView: - SuperView.CanFocus = true; - break; - } - - if (value && _tabIndex == -1) { - TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1; - } - TabStop = value; - - if (!value && SuperView?.Focused == this) { - SuperView._focused = null; - } - if (!value && HasFocus) { - SetHasFocus (false, this); - SuperView?.EnsureFocus (); - if (SuperView != null && SuperView.Focused == null) { - SuperView.FocusNext (); - if (SuperView.Focused == null && Application.Current != null) { - Application.Current.FocusNext (); - } - Application.BringOverlappedTopToFront (); - } - } - if (_subviews != null && IsInitialized) { - foreach (var view in _subviews) { - if (view.CanFocus != value) { - if (!value) { - view._oldCanFocus = view.CanFocus; - view._oldTabIndex = view._tabIndex; - view.CanFocus = false; - view._tabIndex = -1; - } else { - if (_addingView) { - view._addingView = true; - } - view.CanFocus = view._oldCanFocus; - view._tabIndex = view._oldTabIndex; - view._addingView = false; - } - } - } - } - OnCanFocusChanged (); - SetNeedsDisplay (); - } - } + /// + public override bool OnLeave (View view) + { + var args = new FocusEventArgs (view); + Leave?.Invoke (this, args); + if (args.Handled) { + return true; + } + if (base.OnLeave (view)) { + return true; } + Driver?.SetCursorVisibility (CursorVisibility.Invisible); + return false; + } - /// - public override bool OnEnter (View view) - { - var args = new FocusEventArgs (view); - Enter?.Invoke (this, args); - if (args.Handled) { - return true; - } - if (base.OnEnter (view)) { - return true; - } + /// + /// Returns the currently focused view inside this view, or null if nothing is focused. + /// + /// The focused. + public View Focused { get; private set; } - return false; - } - - /// - public override bool OnLeave (View view) - { - var args = new FocusEventArgs (view); - Leave?.Invoke (this, args); - if (args.Handled) { - return true; - } - if (base.OnLeave (view)) { - return true; - } - - Driver?.SetCursorVisibility (CursorVisibility.Invisible); - return false; - } - - /// - /// Returns the currently focused view inside this view, or null if nothing is focused. - /// - /// The focused. - public View Focused => _focused; - - /// - /// Returns the most focused view in the chain of subviews (the leaf view that has the focus). - /// - /// The most focused View. - public View MostFocused { - get { - if (Focused == null) - return null; - var most = Focused.MostFocused; - if (most != null) - return most; - return Focused; - } - } - - /// - /// Causes the specified subview to have focus. - /// - /// View. - void SetFocus (View view) - { - if (view == null) { - return; - } - //Console.WriteLine ($"Request to focus {view}"); - if (!view.CanFocus || !view.Visible || !view.Enabled) { - return; - } - if (_focused?._hasFocus == true && _focused == view) { - return; - } - if ((_focused?._hasFocus == true && _focused?.SuperView == view) || view == this) { - - if (!view._hasFocus) { - view._hasFocus = true; - } - return; - } - // Make sure that this view is a subview - View c; - for (c = view._superView; c != null; c = c._superView) - if (c == this) - break; - if (c == null) - throw new ArgumentException ("the specified view is not part of the hierarchy of this view"); - - if (_focused != null) - _focused.SetHasFocus (false, view); - - var f = _focused; - _focused = view; - _focused.SetHasFocus (true, f); - _focused.EnsureFocus (); - - // Send focus upwards - if (SuperView != null) { - SuperView.SetFocus (this); - } else { - SetFocus (this); - } - } - - /// - /// Causes the specified view and the entire parent hierarchy to have the focused order updated. - /// - public void SetFocus () - { - if (!CanBeVisible (this) || !Enabled) { - if (HasFocus) { - SetHasFocus (false, this); - } - return; - } - - if (SuperView != null) { - SuperView.SetFocus (this); - } else { - SetFocus (this); - } - } - - /// - /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does nothing. - /// - public void EnsureFocus () - { - if (_focused == null && _subviews?.Count > 0) { - if (FocusDirection == Direction.Forward) { - FocusFirst (); - } else { - FocusLast (); - } - } - } - - /// - /// Focuses the first focusable subview if one exists. - /// - public void FocusFirst () - { - if (!CanBeVisible (this)) { - return; - } - - if (_tabIndexes == null) { - SuperView?.SetFocus (this); - return; - } - - foreach (var view in _tabIndexes) { - if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) { - SetFocus (view); - return; - } - } - } - - /// - /// Focuses the last focusable subview if one exists. - /// - public void FocusLast () - { - if (!CanBeVisible (this)) { - return; - } - - if (_tabIndexes == null) { - SuperView?.SetFocus (this); - return; - } - - for (var i = _tabIndexes.Count; i > 0;) { - i--; - - var v = _tabIndexes [i]; - if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) { - SetFocus (v); - return; - } - } - } - - /// - /// Focuses the previous view. - /// - /// if previous was focused, otherwise. - public bool FocusPrev () - { - if (!CanBeVisible (this)) { - return false; - } - - FocusDirection = Direction.Backward; - if (_tabIndexes == null || _tabIndexes.Count == 0) - return false; - - if (_focused == null) { - FocusLast (); - return _focused != null; - } - - var focusedIdx = -1; - for (var i = _tabIndexes.Count; i > 0;) { - i--; - var w = _tabIndexes [i]; - - if (w.HasFocus) { - if (w.FocusPrev ()) - return true; - focusedIdx = i; - continue; - } - if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { - _focused.SetHasFocus (false, w); - - if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) - w.FocusLast (); - - SetFocus (w); - return true; - } - } - if (_focused != null) { - _focused.SetHasFocus (false, this); - _focused = null; - } - return false; - } - - /// - /// Focuses the next view. - /// - /// if next was focused, otherwise. - public bool FocusNext () - { - if (!CanBeVisible (this)) { - return false; - } - - FocusDirection = Direction.Forward; - if (_tabIndexes == null || _tabIndexes.Count == 0) - return false; - - if (_focused == null) { - FocusFirst (); - return _focused != null; - } - var focusedIdx = -1; - for (var i = 0; i < _tabIndexes.Count; i++) { - var w = _tabIndexes [i]; - - if (w.HasFocus) { - if (w.FocusNext ()) - return true; - focusedIdx = i; - continue; - } - if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { - _focused.SetHasFocus (false, w); - - if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) - w.FocusFirst (); - - SetFocus (w); - return true; - } - } - if (_focused != null) { - _focused.SetHasFocus (false, this); - _focused = null; - } - return false; - } - - View GetMostFocused (View view) - { - if (view == null) { + /// + /// Returns the most focused view in the chain of subviews (the leaf view that has the focus). + /// + /// The most focused View. + public View MostFocused { + get { + if (Focused == null) { return null; } + var most = Focused.MostFocused; + if (most != null) { + return most; + } + return Focused; + } + } - return view._focused != null ? GetMostFocused (view._focused) : view; + /// + /// Causes the specified subview to have focus. + /// + /// View. + void SetFocus (View view) + { + if (view == null) { + return; + } + //Console.WriteLine ($"Request to focus {view}"); + if (!view.CanFocus || !view.Visible || !view.Enabled) { + return; + } + if (Focused?._hasFocus == true && Focused == view) { + return; + } + if (Focused?._hasFocus == true && Focused?.SuperView == view || view == this) { + + if (!view._hasFocus) { + view._hasFocus = true; + } + return; + } + // Make sure that this view is a subview + View c; + for (c = view._superView; c != null; c = c._superView) { + if (c == this) { + break; + } + } + if (c == null) { + throw new ArgumentException ("the specified view is not part of the hierarchy of this view"); } - /// - /// Positions the cursor in the right position based on the currently focused view in the chain. - /// - /// Views that are focusable should override to ensure - /// the cursor is placed in a location that makes sense. Unix terminals do not have - /// a way of hiding the cursor, so it can be distracting to have the cursor left at - /// the last focused view. Views should make sure that they place the cursor - /// in a visually sensible place. - public virtual void PositionCursor () - { - if (!CanBeVisible (this) || !Enabled) { + if (Focused != null) { + Focused.SetHasFocus (false, view); + } + + var f = Focused; + Focused = view; + Focused.SetHasFocus (true, f); + Focused.EnsureFocus (); + + // Send focus upwards + if (SuperView != null) { + SuperView.SetFocus (this); + } else { + SetFocus (this); + } + } + + /// + /// Causes the specified view and the entire parent hierarchy to have the focused order updated. + /// + public void SetFocus () + { + if (!CanBeVisible (this) || !Enabled) { + if (HasFocus) { + SetHasFocus (false, this); + } + return; + } + + if (SuperView != null) { + SuperView.SetFocus (this); + } else { + SetFocus (this); + } + } + + /// + /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does + /// nothing. + /// + public void EnsureFocus () + { + if (Focused == null && _subviews?.Count > 0) { + if (FocusDirection == Direction.Forward) { + FocusFirst (); + } else { + FocusLast (); + } + } + } + + /// + /// Focuses the first focusable subview if one exists. + /// + public void FocusFirst () + { + if (!CanBeVisible (this)) { + return; + } + + if (_tabIndexes == null) { + SuperView?.SetFocus (this); + return; + } + + foreach (var view in _tabIndexes) { + if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) { + SetFocus (view); return; } + } + } - // BUGBUG: v2 - This needs to support children of Frames too + /// + /// Focuses the last focusable subview if one exists. + /// + public void FocusLast () + { + if (!CanBeVisible (this)) { + return; + } - if (_focused == null && SuperView != null) { - SuperView.EnsureFocus (); - } else if (_focused?.Visible == true && _focused?.Enabled == true && _focused?.Frame.Width > 0 && _focused.Frame.Height > 0) { - _focused.PositionCursor (); - } else if (_focused?.Visible == true && _focused?.Enabled == false) { - _focused = null; - } else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) { - Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0); - } else { - Move (_frame.X, _frame.Y); + if (_tabIndexes == null) { + SuperView?.SetFocus (this); + return; + } + + for (var i = _tabIndexes.Count; i > 0;) { + i--; + + var v = _tabIndexes [i]; + if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) { + SetFocus (v); + return; } } - #endregion Focus } -} + + /// + /// Focuses the previous view. + /// + /// if previous was focused, otherwise. + public bool FocusPrev () + { + if (!CanBeVisible (this)) { + return false; + } + + FocusDirection = Direction.Backward; + if (_tabIndexes == null || _tabIndexes.Count == 0) { + return false; + } + + if (Focused == null) { + FocusLast (); + return Focused != null; + } + + var focusedIdx = -1; + for (var i = _tabIndexes.Count; i > 0;) { + i--; + var w = _tabIndexes [i]; + + if (w.HasFocus) { + if (w.FocusPrev ()) { + return true; + } + focusedIdx = i; + continue; + } + if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { + Focused.SetHasFocus (false, w); + + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) { + w.FocusLast (); + } + + SetFocus (w); + return true; + } + } + if (Focused != null) { + Focused.SetHasFocus (false, this); + Focused = null; + } + return false; + } + + /// + /// Focuses the next view. + /// + /// if next was focused, otherwise. + public bool FocusNext () + { + if (!CanBeVisible (this)) { + return false; + } + + FocusDirection = Direction.Forward; + if (_tabIndexes == null || _tabIndexes.Count == 0) { + return false; + } + + if (Focused == null) { + FocusFirst (); + return Focused != null; + } + var focusedIdx = -1; + for (var i = 0; i < _tabIndexes.Count; i++) { + var w = _tabIndexes [i]; + + if (w.HasFocus) { + if (w.FocusNext ()) { + return true; + } + focusedIdx = i; + continue; + } + if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) { + Focused.SetHasFocus (false, w); + + if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) { + w.FocusFirst (); + } + + SetFocus (w); + return true; + } + } + if (Focused != null) { + Focused.SetHasFocus (false, this); + Focused = null; + } + return false; + } + + View GetMostFocused (View view) + { + if (view == null) { + return null; + } + + return view.Focused != null ? GetMostFocused (view.Focused) : view; + } + + /// + /// Positions the cursor in the right position based on the currently focused view in the chain. + /// + /// Views that are focusable should override + /// + /// to ensure + /// the cursor is placed in a location that makes sense. Unix terminals do not have + /// a way of hiding the cursor, so it can be distracting to have the cursor left at + /// the last focused view. Views should make sure that they place the cursor + /// in a visually sensible place. + public virtual void PositionCursor () + { + if (!CanBeVisible (this) || !Enabled) { + return; + } + + // BUGBUG: v2 - This needs to support children of Frames too + + if (Focused == null && SuperView != null) { + SuperView.EnsureFocus (); + } else if (Focused?.Visible == true && Focused?.Enabled == true && Focused?.Frame.Width > 0 && Focused.Frame.Height > 0) { + Focused.PositionCursor (); + } else if (Focused?.Visible == true && Focused?.Enabled == false) { + Focused = null; + } else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) { + Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0); + } else { + Move (_frame.X, _frame.Y); + } + } + #endregion Focus +} \ No newline at end of file diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index ce82df182..d8114bdc9 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -190,37 +190,43 @@ public partial class View { } sizeRequired = Bounds.Size; - if (!AutoSize && !string.IsNullOrEmpty (TextFormatter.Text)) { - switch (TextFormatter.IsVerticalDirection (TextDirection)) { - case true: - var colWidth = TextFormatter.GetSumMaxCharWidth (new List { TextFormatter.Text }, 0, 1); - // TODO: v2 - This uses frame.Width; it should only use Bounds - if (_frame.Width < colWidth && - (Width == null || - Bounds.Width >= 0 && - Width is Dim.DimAbsolute && - Width.Anchor (0) >= 0 && - Width.Anchor (0) < colWidth)) { - sizeRequired = new Size (colWidth, Bounds.Height); - return true; - } - break; - default: - if (_frame.Height < 1 && - (Height == null || - Height is Dim.DimAbsolute && - Height.Anchor (0) == 0)) { - sizeRequired = new Size (Bounds.Width, 1); - return true; - } - break; + if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) { + return false; + } + + switch (TextFormatter.IsVerticalDirection (TextDirection)) { + case true: + var colWidth = TextFormatter.GetSumMaxCharWidth (new List { TextFormatter.Text }, 0, 1); + // TODO: v2 - This uses frame.Width; it should only use Bounds + if (_frame.Width < colWidth && + (Width == null || + Bounds.Width >= 0 && + Width is Dim.DimAbsolute && + Width.Anchor (0) >= 0 && + Width.Anchor (0) < colWidth)) { + sizeRequired = new Size (colWidth, Bounds.Height); + return true; } + break; + default: + if (_frame.Height < 1 && + (Height == null || + Height is Dim.DimAbsolute && + Height.Anchor (0) == 0)) { + sizeRequired = new Size (Bounds.Width, 1); + return true; + } + break; } return false; } if (GetMinimumSizeOfText (out var size)) { + // TODO: This is a hack. + //_width = size.Width; + //_height = size.Height; _frame = new Rect (_frame.Location, size); + //throw new InvalidOperationException ("This is a hack."); return true; } return false; @@ -275,10 +281,12 @@ public partial class View { { if (!IsInitialized) { TextFormatter.Size = Size.Empty; + return; } if (string.IsNullOrEmpty (TextFormatter.Text)) { TextFormatter.Size = Bounds.Size; + return; } TextFormatter.Size = new Size (Bounds.Size.Width + GetHotKeySpecifierLength (), @@ -299,9 +307,9 @@ public partial class View { x = Bounds.X; y = Bounds.Y; } - var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); - var newWidth = rect.Size.Width - GetHotKeySpecifierLength () + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal; - var newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical; + var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); + int newWidth = rect.Size.Width - GetHotKeySpecifierLength () + (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal); + int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical); return new Size (newWidth, newHeight); } diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index 40350e9c5..b69418564 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -1,141 +1,116 @@ -// -// Label.cs: Label control -// -// Authors: -// Miguel de Icaza (miguel@gnome.org) -// +using System; -using System; -using System.Text; +namespace Terminal.Gui; + +/// +/// The Label displays a string at a given position and supports multiple lines separated by newline +/// characters. +/// Multi-line Labels support word wrap. +/// +/// +/// The view is functionality identical to and is included for API backwards +/// compatibility. +/// +public class Label : View { + /// + public Label () => SetInitialProperties (); + + /// + public Label (Rect frame, bool autosize = false) : base (frame) => SetInitialProperties (autosize); + + /// + public Label (string text, bool autosize = true) : base (text) => SetInitialProperties (autosize); + + /// + public Label (Rect rect, string text, bool autosize = false) : base (rect, text) => SetInitialProperties (autosize); + + /// + public Label (int x, int y, string text, bool autosize = true) : base (x, y, text) => SetInitialProperties (autosize); + + /// + public Label (string text, TextDirection direction, bool autosize = true) + : base (text, direction) => SetInitialProperties (autosize); + + void SetInitialProperties (bool autosize = true) + { + Height = 1; + AutoSize = autosize; + // Things this view knows how to do + AddCommand (Command.Default, () => { + // BUGBUG: This is a hack, but it does work. + var can = CanFocus; + CanFocus = true; + SetFocus (); + SuperView.FocusNext (); + CanFocus = can; + return true; + }); + AddCommand (Command.Accept, () => AcceptKey ()); + + // Default key bindings for this view + KeyBindings.Add (KeyCode.Space, Command.Accept); + } + + bool AcceptKey () + { + if (!HasFocus) { + SetFocus (); + } + OnClicked (); + return true; + } -namespace Terminal.Gui { /// - /// The Label displays a string at a given position and supports multiple lines separated by newline characters. - /// Multi-line Labels support word wrap. + /// The event fired when the user clicks the primary mouse button within the Bounds of this + /// or if the user presses the action key while this view is focused. (TODO: IsDefault) /// /// - /// The view is functionality identical to and is included for API backwards compatibility. + /// Client code can hook up to this event, it is + /// raised when the button is activated either with + /// the mouse or the keyboard. /// - public class Label : View { - /// - public Label () - { - SetInitialProperties (); + public event EventHandler Clicked; + + /// + /// Method invoked when a mouse event is generated + /// + /// + /// true, if the event was handled, false otherwise. + public override bool OnMouseEvent (MouseEvent mouseEvent) + { + var args = new MouseEventEventArgs (mouseEvent); + if (OnMouseClick (args)) { + return true; + } + if (MouseEvent (mouseEvent)) { + return true; } - /// - public Label (Rect frame, bool autosize = false) : base (frame) - { - SetInitialProperties (autosize); - } - - /// - public Label (string text, bool autosize = true) : base (text) - { - SetInitialProperties (autosize); - } - - /// - public Label (Rect rect, string text, bool autosize = false) : base (rect, text) - { - SetInitialProperties (autosize); - } - - /// - public Label (int x, int y, string text, bool autosize = true) : base (x, y, text) - { - SetInitialProperties (autosize); - } - - /// - public Label (string text, TextDirection direction, bool autosize = true) - : base (text, direction) - { - SetInitialProperties (autosize); - } - - void SetInitialProperties (bool autosize = true) - { - Height = 1; - AutoSize = autosize; - // Things this view knows how to do - AddCommand (Command.Default, () => { - // BUGBUG: This is a hack, but it does work. - var can = CanFocus; - CanFocus = true; - SetFocus (); - SuperView.FocusNext (); - CanFocus = can; - return true; - }); - AddCommand (Command.Accept, () => AcceptKey ()); - - // Default key bindings for this view - KeyBindings.Add (KeyCode.Space, Command.Accept); - } - - bool AcceptKey () - { - if (!HasFocus) { + if (mouseEvent.Flags == MouseFlags.Button1Clicked) { + if (!HasFocus && SuperView != null) { + if (!SuperView.HasFocus) { + SuperView.SetFocus (); + } SetFocus (); + SetNeedsDisplay (); } + OnClicked (); return true; } - - /// - /// The event fired when the user clicks the primary mouse button within the Bounds of this - /// or if the user presses the action key while this view is focused. (TODO: IsDefault) - /// - /// - /// Client code can hook up to this event, it is - /// raised when the button is activated either with - /// the mouse or the keyboard. - /// - public event EventHandler Clicked; - - /// - /// Method invoked when a mouse event is generated - /// - /// - /// true, if the event was handled, false otherwise. - public override bool OnMouseEvent (MouseEvent mouseEvent) - { - MouseEventEventArgs args = new MouseEventEventArgs (mouseEvent); - if (OnMouseClick (args)) - return true; - if (MouseEvent (mouseEvent)) - return true; - - if (mouseEvent.Flags == MouseFlags.Button1Clicked) { - if (!HasFocus && SuperView != null) { - if (!SuperView.HasFocus) { - SuperView.SetFocus (); - } - SetFocus (); - SetNeedsDisplay (); - } - - OnClicked (); - return true; - } - return false; - } - - /// - public override bool OnEnter (View view) - { - Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); - - return base.OnEnter (view); - } - - /// - /// Virtual method to invoke the event. - /// - public virtual void OnClicked () - { - Clicked?.Invoke (this, EventArgs.Empty); - } + return false; } -} + + /// + public override bool OnEnter (View view) + { + Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); + + return base.OnEnter (view); + } + + /// + /// Virtual method to invoke the event. + /// + public virtual void OnClicked () => Clicked?.Invoke (this, EventArgs.Empty); +} \ No newline at end of file diff --git a/UICatalog/Scenarios/ListColumns.cs b/UICatalog/Scenarios/ListColumns.cs index 96b27d7f3..42f0fad3d 100644 --- a/UICatalog/Scenarios/ListColumns.cs +++ b/UICatalog/Scenarios/ListColumns.cs @@ -3,331 +3,358 @@ using System.Collections; using System.Collections.Generic; using System.Data; using Terminal.Gui; -using static Terminal.Gui.TableView; -namespace UICatalog.Scenarios { +namespace UICatalog.Scenarios; - [ScenarioMetadata (Name: "ListColumns", Description: "Implements a columned list via a data table.")] - [ScenarioCategory ("TableView")] - [ScenarioCategory ("Controls")] - [ScenarioCategory ("Dialogs")] - [ScenarioCategory ("Text and Formatting")] - [ScenarioCategory ("Top Level Windows")] - public class ListColumns : Scenario { - TableView listColView; - DataTable currentTable; - private MenuItem _miCellLines; - private MenuItem _miExpandLastColumn; - private MenuItem _miAlwaysUseNormalColorForVerticalCellLines; - private MenuItem _miSmoothScrolling; - private MenuItem _miAlternatingColors; - private MenuItem _miCursor; - private MenuItem _miTopline; - private MenuItem _miBottomline; - private MenuItem _miOrientVertical; - private MenuItem _miScrollParallel; +[ScenarioMetadata ("ListColumns", "Implements a columned list via a data table.")] +[ScenarioCategory ("TableView")] +[ScenarioCategory ("Controls")] +[ScenarioCategory ("Dialogs")] +[ScenarioCategory ("Text and Formatting")] +[ScenarioCategory ("Top Level Windows")] +public class ListColumns : Scenario { + MenuItem _miAlternatingColors; + MenuItem _miAlwaysUseNormalColorForVerticalCellLines; + MenuItem _miBottomline; + MenuItem _miCellLines; + MenuItem _miCursor; + MenuItem _miExpandLastColumn; + MenuItem _miOrientVertical; + MenuItem _miScrollParallel; + MenuItem _miSmoothScrolling; + MenuItem _miTopline; - ColorScheme alternatingColorScheme; + ColorScheme alternatingColorScheme; + DataTable currentTable; + TableView listColView; - public override void Setup () - { - Win.Title = this.GetName (); - Win.Y = 1; // menu - Win.Height = Dim.Fill (1); // status bar + public override void Setup () + { + Win.Title = GetName (); + Win.Y = 1; // menu + Win.Height = Dim.Fill (1); // status bar - this.listColView = new TableView () { - X = 0, - Y = 0, - Width = Dim.Fill (), - Height = Dim.Fill (1), - Style = new TableStyle { - ShowHeaders = false, - ShowHorizontalHeaderOverline = false, - ShowHorizontalHeaderUnderline = false, - ShowHorizontalBottomline = false, - ExpandLastColumn = false, + listColView = new TableView { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill (1), + Style = new TableStyle { + ShowHeaders = false, + ShowHorizontalHeaderOverline = false, + ShowHorizontalHeaderUnderline = false, + ShowHorizontalBottomline = false, + ExpandLastColumn = false + } + }; + var listColStyle = new ListColumnStyle (); + + var menu = new MenuBar (new MenuBarItem [] { + new ("_File", new MenuItem [] { + new ("Open_BigListExample", "", () => OpenSimpleList (true)), + new ("Open_SmListExample", "", () => OpenSimpleList (false)), + new ("_CloseExample", "", () => CloseExample ()), + new ("_Quit", "", () => Quit ()) + }), + new ("_View", new [] { + _miTopline = new MenuItem ("_TopLine", "", () => ToggleTopline ()) { + Checked = listColView.Style.ShowHorizontalHeaderOverline, + CheckType = MenuItemCheckStyle.Checked + }, + _miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline ()) { + Checked = listColView.Style.ShowHorizontalBottomline, + CheckType = MenuItemCheckStyle.Checked + }, + _miCellLines = new MenuItem ("_CellLines", "", () => ToggleCellLines ()) { + Checked = listColView.Style.ShowVerticalCellLines, + CheckType = MenuItemCheckStyle.Checked + }, + _miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn ()) { + Checked = listColView.Style.ExpandLastColumn, + CheckType = MenuItemCheckStyle.Checked + }, + _miAlwaysUseNormalColorForVerticalCellLines = + new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "", + () => ToggleAlwaysUseNormalColorForVerticalCellLines ()) { + Checked = listColView.Style.AlwaysUseNormalColorForVerticalCellLines, + CheckType = MenuItemCheckStyle.Checked + }, + _miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling ()) { + Checked = listColView.Style.SmoothHorizontalScrolling, + CheckType = MenuItemCheckStyle.Checked + }, + _miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors ()) + { CheckType = MenuItemCheckStyle.Checked }, + _miCursor = new MenuItem ("Invert Selected Cell First Character", "", + () => ToggleInvertSelectedCellFirstCharacter ()) { + Checked = listColView.Style.InvertSelectedCellFirstCharacter, + CheckType = MenuItemCheckStyle.Checked } - }; - var listColStyle = new ListColumnStyle (); + }), + new ("_List", new [] { + //new MenuItem ("_Hide Headers", "", HideHeaders), + _miOrientVertical = new MenuItem ("_OrientVertical", "", () => ToggleVerticalOrientation ()) { + Checked = listColStyle.Orientation == Orientation.Vertical, + CheckType = MenuItemCheckStyle.Checked + }, + _miScrollParallel = new MenuItem ("_ScrollParallel", "", () => ToggleScrollParallel ()) + { Checked = listColStyle.ScrollParallel, CheckType = MenuItemCheckStyle.Checked }, + new ("Set _Max Cell Width", "", SetListMaxWidth), + new ("Set Mi_n Cell Width", "", SetListMinWidth) + }) + }); - var menu = new MenuBar (new MenuBarItem [] { - new MenuBarItem ("_File", new MenuItem [] { - new MenuItem ("Open_BigListExample", "", () => OpenSimpleList (true)), - new MenuItem ("Open_SmListExample", "", () => OpenSimpleList (false)), - new MenuItem ("_CloseExample", "", () => CloseExample ()), - new MenuItem ("_Quit", "", () => Quit()), - }), - new MenuBarItem ("_View", new MenuItem [] { - _miTopline = new MenuItem ("_TopLine", "", () => ToggleTopline ()) { Checked = listColView.Style.ShowHorizontalHeaderOverline, CheckType = MenuItemCheckStyle.Checked }, - _miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline ()) { Checked = listColView.Style.ShowHorizontalBottomline, CheckType = MenuItemCheckStyle.Checked }, - _miCellLines = new MenuItem ("_CellLines", "", () => ToggleCellLines ()) { Checked = listColView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, - _miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn ()) { Checked = listColView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked }, - _miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines ()) { Checked = listColView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked }, - _miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling ()) { Checked = listColView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked }, - _miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors ()) { CheckType = MenuItemCheckStyle.Checked}, - _miCursor = new MenuItem ("Invert Selected Cell First Character", "", () => ToggleInvertSelectedCellFirstCharacter ()) { Checked = listColView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked}, - }), - new MenuBarItem ("_List", new MenuItem [] { - //new MenuItem ("_Hide Headers", "", HideHeaders), - _miOrientVertical = new MenuItem ("_OrientVertical", "", () => ToggleVerticalOrientation ()) { Checked = listColStyle.Orientation == Orientation.Vertical, CheckType = MenuItemCheckStyle.Checked }, - _miScrollParallel = new MenuItem ("_ScrollParallel", "", () => ToggleScrollParallel ()) { Checked = listColStyle.ScrollParallel, CheckType = MenuItemCheckStyle.Checked }, - new MenuItem ("Set _Max Cell Width", "", SetListMaxWidth), - new MenuItem ("Set Mi_n Cell Width", "", SetListMinWidth), - }), - }); + Application.Top.Add (menu); - Application.Top.Add (menu); + var statusBar = new StatusBar (new StatusItem [] { + new (KeyCode.F2, "~F2~ OpenBigListEx", () => OpenSimpleList (true)), + new (KeyCode.F3, "~F3~ CloseExample", () => CloseExample ()), + new (KeyCode.F4, "~F4~ OpenSmListEx", () => OpenSimpleList (false)), + new (Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit ()) + }); + Application.Top.Add (statusBar); - var statusBar = new StatusBar (new StatusItem [] { - new StatusItem(KeyCode.F2, "~F2~ OpenBigListEx", () => OpenSimpleList (true)), - new StatusItem(KeyCode.F3, "~F3~ CloseExample", () => CloseExample ()), - new StatusItem(KeyCode.F4, "~F4~ OpenSmListEx", () => OpenSimpleList (false)), - new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()), - }); - Application.Top.Add (statusBar); + Win.Add (listColView); - Win.Add (listColView); + var selectedCellLabel = new Label { + X = 0, + Y = Pos.Bottom (listColView), + Text = "0,0", + Width = Dim.Fill (), + TextAlignment = TextAlignment.Right - var selectedCellLabel = new Label () { - X = 0, - Y = Pos.Bottom (listColView), - Text = "0,0", - Width = Dim.Fill (), - TextAlignment = TextAlignment.Right + }; - }; + Win.Add (selectedCellLabel); - Win.Add (selectedCellLabel); + listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{listColView.SelectedRow},{listColView.SelectedColumn}"; }; + listColView.KeyDown += TableViewKeyPress; - listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{listColView.SelectedRow},{listColView.SelectedColumn}"; }; - listColView.KeyDown += TableViewKeyPress; + SetupScrollBar (); - SetupScrollBar (); + alternatingColorScheme = new ColorScheme { - alternatingColorScheme = new ColorScheme () { + Disabled = Win.ColorScheme.Disabled, + HotFocus = Win.ColorScheme.HotFocus, + Focus = Win.ColorScheme.Focus, + Normal = new Attribute (Color.White, Color.BrightBlue) + }; - Disabled = Win.ColorScheme.Disabled, - HotFocus = Win.ColorScheme.HotFocus, - Focus = Win.ColorScheme.Focus, - Normal = new Attribute (Color.White, Color.BrightBlue) - }; + // if user clicks the mouse in TableView + listColView.MouseClick += (s, e) => { - // if user clicks the mouse in TableView - listColView.MouseClick += (s, e) => { + listColView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out var clickedCol); + }; - listColView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol); - }; + listColView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked); + } - listColView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked); - } + void SetupScrollBar () + { + var scrollBar = new ScrollBarView (listColView, true); // (listColView, true, true); - private void SetupScrollBar () - { - var scrollBar = new ScrollBarView (listColView, true); // (listColView, true, true); - - scrollBar.ChangedPosition += (s, e) => { - listColView.RowOffset = scrollBar.Position; - if (listColView.RowOffset != scrollBar.Position) { - scrollBar.Position = listColView.RowOffset; - } - listColView.SetNeedsDisplay (); - }; - /* - scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => { - listColView.ColumnOffset = scrollBar.OtherScrollBarView.Position; - if (listColView.ColumnOffset != scrollBar.OtherScrollBarView.Position) { - scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset; - } - listColView.SetNeedsDisplay (); - }; - */ - - listColView.DrawContent += (s, e) => { - scrollBar.Size = listColView.Table?.Rows ?? 0; + scrollBar.ChangedPosition += (s, e) => { + listColView.RowOffset = scrollBar.Position; + if (listColView.RowOffset != scrollBar.Position) { scrollBar.Position = listColView.RowOffset; - //scrollBar.OtherScrollBarView.Size = listColView.Table?.Columns - 1 ?? 0; - //scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset; - scrollBar.Refresh (); - }; - - } - - private void TableViewKeyPress (object sender, Key e) - { - if (e.KeyCode == KeyCode.Delete) { - - // set all selected cells to null - foreach (var pt in listColView.GetAllSelectedCells ()) { - currentTable.Rows [pt.Y] [pt.X] = DBNull.Value; - } - - listColView.Update (); - e.Handled = true; - } - - } - - private void ToggleTopline () - { - _miTopline.Checked = !_miTopline.Checked; - listColView.Style.ShowHorizontalHeaderOverline = (bool)_miTopline.Checked; - listColView.Update (); - } - private void ToggleBottomline () - { - _miBottomline.Checked = !_miBottomline.Checked; - listColView.Style.ShowHorizontalBottomline = (bool)_miBottomline.Checked; - listColView.Update (); - } - private void ToggleExpandLastColumn () - { - _miExpandLastColumn.Checked = !_miExpandLastColumn.Checked; - listColView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked; - - listColView.Update (); - - } - - private void ToggleAlwaysUseNormalColorForVerticalCellLines () - { - _miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked; - listColView.Style.AlwaysUseNormalColorForVerticalCellLines = (bool)_miAlwaysUseNormalColorForVerticalCellLines.Checked; - - listColView.Update (); - } - private void ToggleSmoothScrolling () - { - _miSmoothScrolling.Checked = !_miSmoothScrolling.Checked; - listColView.Style.SmoothHorizontalScrolling = (bool)_miSmoothScrolling.Checked; - - listColView.Update (); - - } - private void ToggleCellLines () - { - _miCellLines.Checked = !_miCellLines.Checked; - listColView.Style.ShowVerticalCellLines = (bool)_miCellLines.Checked; - listColView.Update (); - } - private void ToggleAlternatingColors () - { - //toggle menu item - _miAlternatingColors.Checked = !_miAlternatingColors.Checked; - - if (_miAlternatingColors.Checked == true) { - listColView.Style.RowColorGetter = (a) => { return a.RowIndex % 2 == 0 ? alternatingColorScheme : null; }; - } else { - listColView.Style.RowColorGetter = null; } listColView.SetNeedsDisplay (); - } - - private void ToggleInvertSelectedCellFirstCharacter () - { - //toggle menu item - _miCursor.Checked = !_miCursor.Checked; - listColView.Style.InvertSelectedCellFirstCharacter = (bool)_miCursor.Checked; + }; + /* + scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => { + listColView.ColumnOffset = scrollBar.OtherScrollBarView.Position; + if (listColView.ColumnOffset != scrollBar.OtherScrollBarView.Position) { + scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset; + } listColView.SetNeedsDisplay (); - } + }; + */ - private void ToggleVerticalOrientation () - { - _miOrientVertical.Checked = !_miOrientVertical.Checked; - if ((ListTableSource)listColView.Table != null) { - ((ListTableSource)listColView.Table).Style.Orientation = (bool)_miOrientVertical.Checked ? Orientation.Vertical : Orientation.Horizontal; - listColView.SetNeedsDisplay (); + listColView.DrawContent += (s, e) => { + scrollBar.Size = listColView.Table?.Rows ?? 0; + scrollBar.Position = listColView.RowOffset; + //scrollBar.OtherScrollBarView.Size = listColView.Table?.Columns - 1 ?? 0; + //scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset; + scrollBar.Refresh (); + }; + + } + + void TableViewKeyPress (object sender, Key e) + { + if (e.KeyCode == KeyCode.Delete) { + + // set all selected cells to null + foreach (var pt in listColView.GetAllSelectedCells ()) { + currentTable.Rows [pt.Y] [pt.X] = DBNull.Value; } + + listColView.Update (); + e.Handled = true; } - private void ToggleScrollParallel () - { - _miScrollParallel.Checked = !_miScrollParallel.Checked; - if ((ListTableSource)listColView.Table != null) { - ((ListTableSource)listColView.Table).Style.ScrollParallel = (bool)_miScrollParallel.Checked; - listColView.SetNeedsDisplay (); - } - } + } - private void SetListMinWidth () - { - RunListWidthDialog ("MinCellWidth", (s, v) => s.MinCellWidth = v, (s) => s.MinCellWidth); + void ToggleTopline () + { + _miTopline.Checked = !_miTopline.Checked; + listColView.Style.ShowHorizontalHeaderOverline = (bool)_miTopline.Checked; + listColView.Update (); + } + + void ToggleBottomline () + { + _miBottomline.Checked = !_miBottomline.Checked; + listColView.Style.ShowHorizontalBottomline = (bool)_miBottomline.Checked; + listColView.Update (); + } + + void ToggleExpandLastColumn () + { + _miExpandLastColumn.Checked = !_miExpandLastColumn.Checked; + listColView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked; + + listColView.Update (); + + } + + void ToggleAlwaysUseNormalColorForVerticalCellLines () + { + _miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked; + listColView.Style.AlwaysUseNormalColorForVerticalCellLines = (bool)_miAlwaysUseNormalColorForVerticalCellLines.Checked; + + listColView.Update (); + } + + void ToggleSmoothScrolling () + { + _miSmoothScrolling.Checked = !_miSmoothScrolling.Checked; + listColView.Style.SmoothHorizontalScrolling = (bool)_miSmoothScrolling.Checked; + + listColView.Update (); + + } + + void ToggleCellLines () + { + _miCellLines.Checked = !_miCellLines.Checked; + listColView.Style.ShowVerticalCellLines = (bool)_miCellLines.Checked; + listColView.Update (); + } + + void ToggleAlternatingColors () + { + //toggle menu item + _miAlternatingColors.Checked = !_miAlternatingColors.Checked; + + if (_miAlternatingColors.Checked == true) { + listColView.Style.RowColorGetter = a => { return a.RowIndex % 2 == 0 ? alternatingColorScheme : null; }; + } else { + listColView.Style.RowColorGetter = null; + } + listColView.SetNeedsDisplay (); + } + + void ToggleInvertSelectedCellFirstCharacter () + { + //toggle menu item + _miCursor.Checked = !_miCursor.Checked; + listColView.Style.InvertSelectedCellFirstCharacter = (bool)_miCursor.Checked; + listColView.SetNeedsDisplay (); + } + + void ToggleVerticalOrientation () + { + _miOrientVertical.Checked = !_miOrientVertical.Checked; + if ((ListTableSource)listColView.Table != null) { + ((ListTableSource)listColView.Table).Style.Orientation = (bool)_miOrientVertical.Checked ? Orientation.Vertical : Orientation.Horizontal; listColView.SetNeedsDisplay (); } - - private void SetListMaxWidth () - { - RunListWidthDialog ("MaxCellWidth", (s, v) => s.MaxCellWidth = v, (s) => s.MaxCellWidth); - listColView.SetNeedsDisplay (); - } - - private void RunListWidthDialog (string prompt, Action setter, Func getter) - { - var accepted = false; - var ok = new Button ("Ok", is_default: true); - ok.Clicked += (s, e) => { accepted = true; Application.RequestStop (); }; - var cancel = new Button ("Cancel"); - cancel.Clicked += (s, e) => { Application.RequestStop (); }; - var d = new Dialog (ok, cancel) { Title = prompt }; - - var tf = new TextField () { - Text = getter (listColView).ToString (), - X = 0, - Y = 1, - Width = Dim.Fill () - }; - - d.Add (tf); - tf.SetFocus (); - - Application.Run (d); - - if (accepted) { - - try { - setter (listColView, int.Parse (tf.Text)); - } catch (Exception ex) { - MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok"); - } - } - } - - private void CloseExample () - { - listColView.Table = null; - } - - private void Quit () - { - Application.RequestStop (); - } - - private void OpenSimpleList (bool big) - { - SetTable (BuildSimpleList (big ? 1023 : 31)); - } - - private void SetTable (IList list) - { - listColView.Table = new ListTableSource (list, listColView); - if ((ListTableSource)listColView.Table != null) { - currentTable = ((ListTableSource)listColView.Table).DataTable; - } - } - - /// - /// Builds a simple list in which values are the index. This helps testing that scrolling etc is working correctly and not skipping out values when paging - /// - /// - /// - public static IList BuildSimpleList (int items) - { - var list = new List (); - - for (int i = 0; i < items; i++) { - list.Add ("Item " + i); - } - - return list; - } + } + + void ToggleScrollParallel () + { + _miScrollParallel.Checked = !_miScrollParallel.Checked; + if ((ListTableSource)listColView.Table != null) { + ((ListTableSource)listColView.Table).Style.ScrollParallel = (bool)_miScrollParallel.Checked; + listColView.SetNeedsDisplay (); + } + } + + void SetListMinWidth () + { + RunListWidthDialog ("MinCellWidth", (s, v) => s.MinCellWidth = v, s => s.MinCellWidth); + listColView.SetNeedsDisplay (); + } + + void SetListMaxWidth () + { + RunListWidthDialog ("MaxCellWidth", (s, v) => s.MaxCellWidth = v, s => s.MaxCellWidth); + listColView.SetNeedsDisplay (); + } + + void RunListWidthDialog (string prompt, Action setter, Func getter) + { + var accepted = false; + var ok = new Button ("Ok", true); + ok.Clicked += (s, e) => { + accepted = true; + Application.RequestStop (); + }; + var cancel = new Button ("Cancel"); + cancel.Clicked += (s, e) => { Application.RequestStop (); }; + var d = new Dialog (ok, cancel) { Title = prompt }; + + var tf = new TextField { + Text = getter (listColView).ToString (), + X = 0, + Y = 1, + Width = Dim.Fill () + }; + + d.Add (tf); + tf.SetFocus (); + + Application.Run (d); + + if (accepted) { + + try { + setter (listColView, int.Parse (tf.Text)); + } catch (Exception ex) { + MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok"); + } + } + } + + void CloseExample () => listColView.Table = null; + + void Quit () => Application.RequestStop (); + + void OpenSimpleList (bool big) => SetTable (BuildSimpleList (big ? 1023 : 31)); + + void SetTable (IList list) + { + listColView.Table = new ListTableSource (list, listColView); + if ((ListTableSource)listColView.Table != null) { + currentTable = ((ListTableSource)listColView.Table).DataTable; + } + } + + /// + /// Builds a simple list in which values are the index. This helps testing that scrolling etc is working correctly and not + /// skipping out values when paging + /// + /// + /// + public static IList BuildSimpleList (int items) + { + var list = new List (); + + for (var i = 0; i < items; i++) { + list.Add ("Item " + i); + } + + return list; } } \ No newline at end of file diff --git a/UnitTests/Dialogs/DialogTests.cs b/UnitTests/Dialogs/DialogTests.cs index 8d2a45046..97a6aaeff 100644 --- a/UnitTests/Dialogs/DialogTests.cs +++ b/UnitTests/Dialogs/DialogTests.cs @@ -1,167 +1,132 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Threading.Tasks; -using Terminal.Gui; -using Xunit; -using System.Globalization; +using Xunit; using Xunit.Abstractions; -using System.Text; using static Terminal.Gui.Application; -namespace Terminal.Gui.DialogTests { +namespace Terminal.Gui.DialogTests; - public class DialogTests { - readonly ITestOutputHelper output; +public class DialogTests { + readonly ITestOutputHelper output; - public DialogTests (ITestOutputHelper output) - { - this.output = output; - } + public DialogTests (ITestOutputHelper output) => this.output = output; - //[Fact] - //[AutoInitShutdown] - //public void Default_Has_Border () - //{ - // var d = (FakeDriver)Application.Driver; - // d.SetBufferSize (20, 5); - // Application.RunState runstate = null; + (RunState, Dialog) RunButtonTestDialog (string title, int width, Dialog.ButtonAlignments align, params Button [] btns) + { + var dlg = new Dialog (btns) { + Title = title, + X = 0, + Y = 0, + Width = width, + Height = 1, + ButtonAlignment = align + }; + // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..) + dlg.Border.Thickness = new Thickness (1, 0, 1, 0); + return (Begin (dlg), dlg); + } - // var title = "Title"; - // var btnText = "ok"; - // var buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; - // var width = buttonRow.Length; - // var topRow = $"┌┤{title} {new string (d.HLine.ToString () [0], width - title.Length - 2)}├┐"; - // var bottomRow = $"└{new string (d.HLine.ToString () [0], width - 2)}┘"; + [Fact] + [AutoInitShutdown] + public void Size_Default () + { + var d = new Dialog (); + Begin (d); + ((FakeDriver)Driver).SetBufferSize (100, 100); - // var dlg = new Dialog (title, new Button (btnText)); - // Application.Begin (dlg); + // Default size is Percent(85) + Assert.Equal (new Size ((int)(100 * .85), (int)(100 * .85)), d.Frame.Size); + } - // TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); - // Application.End (runstate); - //} + [Fact] + [AutoInitShutdown] + public void Location_Default () + { + var d = new Dialog (); + Begin (d); + ((FakeDriver)Driver).SetBufferSize (100, 100); - private (RunState, Dialog) RunButtonTestDialog (string title, int width, Dialog.ButtonAlignments align, params Button [] btns) - { - var dlg = new Dialog (btns) { - Title = title, - X = 0, - Y = 0, - Width = width, - Height = 1, - ButtonAlignment = align, - }; - // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..) - dlg.Border.Thickness = new Thickness (1, 0, 1, 0); - return (Application.Begin (dlg), dlg); - } + // Default location is centered, so 100 / 2 - 85 / 2 = 7 + var expected = 7; + Assert.Equal (new Point (expected, expected), d.Frame.Location); + } - [Fact] - [AutoInitShutdown] - public void Size_Default () - { - var d = new Dialog () { - }; - Application.Begin (d); - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); + [Fact] + [AutoInitShutdown] + public void Size_Not_Default () + { + var d = new Dialog { + Width = 50, + Height = 50 + }; - // Default size is Percent(85) - Assert.Equal (new Size ((int)(100 * .85), (int)(100 * .85)), d.Frame.Size); - } + Begin (d); + ((FakeDriver)Driver).SetBufferSize (100, 100); - [Fact] - [AutoInitShutdown] - public void Location_Default () - { - var d = new Dialog () { - }; - Application.Begin (d); - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); + // Default size is Percent(85) + Assert.Equal (new Size (50, 50), d.Frame.Size); + } - // Default location is centered, so 100 / 2 - 85 / 2 = 7 - var expected = 7; - Assert.Equal (new Point (expected, expected), d.Frame.Location); - } + [Fact] + [AutoInitShutdown] + public void Location_Not_Default () + { + var d = new Dialog { + X = 1, + Y = 1 + }; + Begin (d); + ((FakeDriver)Driver).SetBufferSize (100, 100); - [Fact] - [AutoInitShutdown] - public void Size_Not_Default () - { - var d = new Dialog () { - Width = 50, - Height = 50, - }; + // Default location is centered, so 100 / 2 - 85 / 2 = 7 + var expected = 1; + Assert.Equal (new Point (expected, expected), d.Frame.Location); + } - Application.Begin (d); - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); + [Fact] + [AutoInitShutdown] + public void Location_When_Application_Top_Not_Default () + { + var expected = 5; + var d = new Dialog { + X = expected, + Y = expected, + Height = 5, + Width = 5 + }; + Begin (d); + ((FakeDriver)Driver).SetBufferSize (20, 10); - // Default size is Percent(85) - Assert.Equal (new Size (50, 50), d.Frame.Size); - } + // Default location is centered, so 100 / 2 - 85 / 2 = 7 + Assert.Equal (new Point (expected, expected), d.Frame.Location); - [Fact] - [AutoInitShutdown] - public void Location_Not_Default () - { - var d = new Dialog () { - X = 1, - Y = 1, - }; - Application.Begin (d); - ((FakeDriver)Application.Driver).SetBufferSize (100, 100); - - // Default location is centered, so 100 / 2 - 85 / 2 = 7 - var expected = 1; - Assert.Equal (new Point (expected, expected), d.Frame.Location); - } - - [Fact] - [AutoInitShutdown] - public void Location_When_Application_Top_Not_Default () - { - var expected = 5; - var d = new Dialog () { - X = expected, - Y = expected, - Height = 5, - Width = 5 - }; - Application.Begin (d); - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); - - // Default location is centered, so 100 / 2 - 85 / 2 = 7 - Assert.Equal (new Point (expected, expected), d.Frame.Location); - - TestHelpers.AssertDriverContentsWithFrameAre (@" + TestHelpers.AssertDriverContentsWithFrameAre (@" ┌───┐ │ │ │ │ │ │ └───┘", output); - } + } - [Fact] - [AutoInitShutdown] - public void Location_When_Not_Application_Top_Not_Default () - { - Application.Top.BorderStyle = LineStyle.Double; + [Fact] + [AutoInitShutdown] + public void Location_When_Not_Application_Top_Not_Default () + { + Top.BorderStyle = LineStyle.Double; - var iterations = -1; - Application.Iteration += (s, a) => { - iterations++; + var iterations = -1; + Iteration += (s, a) => { + iterations++; - if (iterations == 0) { - var d = new Dialog () { - X = 5, - Y = 5, - Height = 3, - Width = 5 - }; - Application.Begin (d); + if (iterations == 0) { + var d = new Dialog { + X = 5, + Y = 5, + Height = 3, + Width = 5 + }; + Begin (d); - Assert.Equal (new Point (5, 5), d.Frame.Location); - TestHelpers.AssertDriverContentsWithFrameAre (@" + Assert.Equal (new Point (5, 5), d.Frame.Location); + TestHelpers.AssertDriverContentsWithFrameAre (@" ╔══════════════════╗ ║ ║ ║ ║ @@ -173,31 +138,31 @@ namespace Terminal.Gui.DialogTests { ║ ║ ╚══════════════════╝", output); - d = new Dialog () { - X = 5, - Y = 5, - }; - Application.Begin (d); + d = new Dialog { + X = 5, + Y = 5 + }; + Begin (d); - // This is because of PostionTopLevels and EnsureVisibleBounds - Assert.Equal (new Point (3, 2), d.Frame.Location); - // #3127: Before - // Assert.Equal (new Size (17, 8), d.Frame.Size); - // TestHelpers.AssertDriverContentsWithFrameAre (@" - //╔══════════════════╗ - //║ ║ - //║ ┌───────────────┐ - //║ │ │ - //║ │ │ - //║ │ │ - //║ │ │ - //║ │ │ - //║ │ │ - //╚══└───────────────┘", output); + // This is because of PostionTopLevels and EnsureVisibleBounds + Assert.Equal (new Point (3, 2), d.Frame.Location); + // #3127: Before + // Assert.Equal (new Size (17, 8), d.Frame.Size); + // TestHelpers.AssertDriverContentsWithFrameAre (@" + //╔══════════════════╗ + //║ ║ + //║ ┌───────────────┐ + //║ │ │ + //║ │ │ + //║ │ │ + //║ │ │ + //║ │ │ + //║ │ │ + //╚══└───────────────┘", output); - // #3127: After: Because Toplevel is now Width/Height = Dim.Filll - Assert.Equal (new Size (15, 6), d.Frame.Size); - TestHelpers.AssertDriverContentsWithFrameAre (@" + // #3127: After: Because Toplevel is now Width/Height = Dim.Filll + Assert.Equal (new Size (15, 6), d.Frame.Size); + TestHelpers.AssertDriverContentsWithFrameAre (@" ╔══════════════════╗ ║ ║ ║ ┌─────────────┐ ║ @@ -209,622 +174,626 @@ namespace Terminal.Gui.DialogTests { ║ ║ ╚══════════════════╝", output); - } else if (iterations > 0) { - Application.RequestStop (); - } - }; - - Application.Begin (Application.Top); - ((FakeDriver)Application.Driver).SetBufferSize (20, 10); - Application.Run (); - } - - [Fact] - [AutoInitShutdown] - public void ButtonAlignment_One () - { - var d = (FakeDriver)Application.Driver; - RunState runstate = null; - - var title = "1234"; - // E.g "|[ ok ]|" - var btnText = "ok"; - var buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; - var width = buttonRow.Length; - - d.SetBufferSize (width, 1); - - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); - // Center - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Justify - buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Right - buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Left - buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Wider - buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; - width = buttonRow.Length; - - d.SetBufferSize (width, 1); - - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Justify - buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Right - buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Left - buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - } - - [Fact] - [AutoInitShutdown] - public void ButtonAlignment_Two () - { - RunState runstate = null; - - var d = (FakeDriver)Application.Driver; - - var title = "1234"; - // E.g "|[ yes ][ no ]|" - var btn1Text = "yes"; - var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; - var btn2Text = "no"; - var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; - - var buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2} {CM.Glyphs.VLine}"; - var width = buttonRow.Length; - - d.SetBufferSize (buttonRow.Length, 3); - - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Justify - buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Right - buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Left - buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2} {CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - } - - [Fact] - [AutoInitShutdown] - public void ButtonAlignment_Two_Hidden () - { - RunState runstate = null; - bool firstIteration = false; - - var d = (FakeDriver)Application.Driver; - - var title = "1234"; - // E.g "|[ yes ][ no ]|" - var btn1Text = "yes"; - var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; - var btn2Text = "no"; - var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; - - var buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2} {CM.Glyphs.VLine}"; - var width = buttonRow.Length; - - d.SetBufferSize (buttonRow.Length, 3); - - Dialog dlg = null; - Button button1, button2; - - // Default (Center) - button1 = new Button (btn1Text); - button2 = new Button (btn2Text); - (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, button1, button2); - button1.Visible = false; - Application.RunIteration (ref runstate, ref firstIteration); - buttonRow = $@"{CM.Glyphs.VLine} {btn2} {CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Justify - Assert.Equal (width, buttonRow.Length); - button1 = new Button (btn1Text); - button2 = new Button (btn2Text); - (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, button1, button2); - button1.Visible = false; - Application.RunIteration (ref runstate, ref firstIteration); - buttonRow = $@"{CM.Glyphs.VLine} {btn2}{CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Right - Assert.Equal (width, buttonRow.Length); - button1 = new Button (btn1Text); - button2 = new Button (btn2Text); - (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, button1, button2); - button1.Visible = false; - Application.RunIteration (ref runstate, ref firstIteration); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Left - Assert.Equal (width, buttonRow.Length); - button1 = new Button (btn1Text); - button2 = new Button (btn2Text); - (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, button1, button2); - button1.Visible = false; - Application.RunIteration (ref runstate, ref firstIteration); - buttonRow = $@"{CM.Glyphs.VLine} {btn2} {CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - } - - [Fact] - [AutoInitShutdown] - public void ButtonAlignment_Three () - { - RunState runstate = null; - - var d = (FakeDriver)Application.Driver; - - var title = "1234"; - // E.g "|[ yes ][ no ][ maybe ]|" - var btn1Text = "yes"; - var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; - var btn2Text = "no"; - var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; - var btn3Text = "maybe"; - var btn3 = $"{CM.Glyphs.LeftBracket} {btn3Text} {CM.Glyphs.RightBracket}"; - - var buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {CM.Glyphs.VLine}"; - var width = buttonRow.Length; - - d.SetBufferSize (buttonRow.Length, 3); - - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Justify - buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2} {btn3}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Right - buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2} {btn3}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Left - buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - } - - [Fact] - [AutoInitShutdown] - public void ButtonAlignment_Four () - { - RunState runstate = null; - - var d = (FakeDriver)Application.Driver; - - var title = "1234"; - - // E.g "|[ yes ][ no ][ maybe ]|" - var btn1Text = "yes"; - var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; - var btn2Text = "no"; - var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; - var btn3Text = "maybe"; - var btn3 = $"{CM.Glyphs.LeftBracket} {btn3Text} {CM.Glyphs.RightBracket}"; - var btn4Text = "never"; - var btn4 = $"{CM.Glyphs.LeftBracket} {btn4Text} {CM.Glyphs.RightBracket}"; - - var buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; - var width = buttonRow.Length; - d.SetBufferSize (buttonRow.Length, 3); - - // Default - Center - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Justify - buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Right - buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Left - buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - } - - [Fact] - [AutoInitShutdown] - public void ButtonAlignment_Four_On_Too_Small_Width () - { - RunState runstate = null; - - var d = (FakeDriver)Application.Driver; - - var title = "1234"; - - // E.g "|[ yes ][ no ][ maybe ][ never ]|" - var btn1Text = "yes"; - var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; - var btn2Text = "no"; - var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; - var btn3Text = "maybe"; - var btn3 = $"{CM.Glyphs.LeftBracket} {btn3Text} {CM.Glyphs.RightBracket}"; - var btn4Text = "never"; - var btn4 = $"{CM.Glyphs.LeftBracket} {btn4Text} {CM.Glyphs.RightBracket}"; - var buttonRow = string.Empty; - - var width = 30; - d.SetBufferSize (width, 1); - - // Default - Center - buttonRow = $"{CM.Glyphs.VLine}es {CM.Glyphs.RightBracket} {btn2} {btn3} {CM.Glyphs.LeftBracket} neve{CM.Glyphs.VLine}"; - (runstate, var dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - Assert.Equal (new Size (width, 1), dlg.Frame.Size); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Justify - buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.LeftBracket} yes {CM.Glyphs.LeftBracket} no {CM.Glyphs.LeftBracket} maybe {CM.Glyphs.LeftBracket} never {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); Application.End (runstate); - - // Right - buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.RightBracket} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); Application.End (runstate); - - // Left - buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {CM.Glyphs.LeftBracket} n{CM.Glyphs.VLine}"; - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); Application.End (runstate); - } - - [Fact] - [AutoInitShutdown] - public void ButtonAlignment_Four_Wider () - { - RunState runstate = null; - - var d = (FakeDriver)Application.Driver; - - var title = "1234"; - - // E.g "|[ yes ][ no ][ maybe ]|" - var btn1Text = "yes"; - var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; - var btn2Text = "no"; - var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; - var btn3Text = "你你你你你"; // This is a wide char - var btn3 = $"{CM.Glyphs.LeftBracket} {btn3Text} {CM.Glyphs.RightBracket}"; - // Requires a Nerd Font - var btn4Text = "\uE36E\uE36F\uE370\uE371\uE372\uE373"; - var btn4 = $"{CM.Glyphs.LeftBracket} {btn4Text} {CM.Glyphs.RightBracket}"; - - // Note extra spaces to make dialog even wider - // 123456 123456 - var buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; - var width = buttonRow.GetColumns (); - d.SetBufferSize (width, 3); - - // Default - Center - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Justify - buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.GetColumns ()); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Right - buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.GetColumns ()); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Left - buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.GetColumns ()); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - } - - [Fact] - [AutoInitShutdown] - public void ButtonAlignment_Four_WideOdd () - { - RunState runstate = null; - - var d = (FakeDriver)Application.Driver; - - var title = "1234"; - - // E.g "|[ yes ][ no ][ maybe ]|" - var btn1Text = "really long button 1"; - var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; - var btn2Text = "really long button 2"; - var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; - var btn3Text = "really long button 3"; - var btn3 = $"{CM.Glyphs.LeftBracket} {btn3Text} {CM.Glyphs.RightBracket}"; - var btn4Text = "really long button 44"; // 44 is intentional to make length different than rest - var btn4 = $"{CM.Glyphs.LeftBracket} {btn4Text} {CM.Glyphs.RightBracket}"; - - // Note extra spaces to make dialog even wider - // 123456 1234567 - var buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; - var width = buttonRow.Length; - d.SetBufferSize (buttonRow.Length, 1); - - // Default - Center - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Justify - buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Right - buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Left - buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; - Assert.Equal (width, buttonRow.Length); - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - } - - [Fact] - [AutoInitShutdown] - public void Zero_Buttons_Works () - { - RunState runstate = null; - - var d = (FakeDriver)Application.Driver; - - var title = "1234"; - - var buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.VLine}"; - var width = buttonRow.Length; - d.SetBufferSize (buttonRow.Length, 3); - - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - - Application.End (runstate); - } - - [Fact] - [AutoInitShutdown] - public void One_Button_Works () - { - RunState runstate = null; - - var d = (FakeDriver)Application.Driver; - - var title = ""; - var btnText = "ok"; - var buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; - - var width = buttonRow.Length; - d.SetBufferSize (buttonRow.Length, 10); - - (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - } - - [Fact] - [AutoInitShutdown] - public void Add_Button_Works () - { - RunState runstate = null; - - var d = (FakeDriver)Application.Driver; - - var title = "1234"; - var btn1Text = "yes"; - var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; - var btn2Text = "no"; - var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; - - // We test with one button first, but do this to get the width right for 2 - var width = $@"{CM.Glyphs.VLine} {btn1} {btn2} {CM.Glyphs.VLine}".Length; - d.SetBufferSize (width, 1); - - // Default (center) - var dlg = new Dialog (new Button (btn1Text)) { Title = title, Width = width, Height = 1, ButtonAlignment = Dialog.ButtonAlignments.Center }; - // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..) - dlg.Border.Thickness = new Thickness (1, 0, 1, 0); - runstate = Application.Begin (dlg); - var buttonRow = $"{CM.Glyphs.VLine} {btn1} {CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - - // Now add a second button - buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {CM.Glyphs.VLine}"; - dlg.AddButton (new Button (btn2Text)); - bool first = false; - Application.RunIteration (ref runstate, ref first); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Justify - dlg = new Dialog (new Button (btn1Text)) { Title = title, Width = width, Height = 1, ButtonAlignment = Dialog.ButtonAlignments.Justify }; - // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..) - dlg.Border.Thickness = new Thickness (1, 0, 1, 0); - runstate = Application.Begin (dlg); - buttonRow = $"{CM.Glyphs.VLine} {btn1}{CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - - // Now add a second button - buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2}{CM.Glyphs.VLine}"; - dlg.AddButton (new Button (btn2Text)); - first = false; - Application.RunIteration (ref runstate, ref first); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Right - dlg = new Dialog (new Button (btn1Text)) { Title = title, Width = width, Height = 1, ButtonAlignment = Dialog.ButtonAlignments.Right }; - // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..) - dlg.Border.Thickness = new Thickness (1, 0, 1, 0); - runstate = Application.Begin (dlg); - buttonRow = $"{CM.Glyphs.VLine}{new string (' ', width - btn1.Length - 2)}{btn1}{CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - - // Now add a second button - buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2}{CM.Glyphs.VLine}"; - dlg.AddButton (new Button (btn2Text)); - first = false; - Application.RunIteration (ref runstate, ref first); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - - // Left - dlg = new Dialog (new Button (btn1Text)) { Title = title, Width = width, Height = 1, ButtonAlignment = Dialog.ButtonAlignments.Left }; - // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..) - dlg.Border.Thickness = new Thickness (1, 0, 1, 0); - runstate = Application.Begin (dlg); - buttonRow = $"{CM.Glyphs.VLine}{btn1}{new string (' ', width - btn1.Length - 2)}{CM.Glyphs.VLine}"; - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - - // Now add a second button - buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {CM.Glyphs.VLine}"; - dlg.AddButton (new Button (btn2Text)); - first = false; - Application.RunIteration (ref runstate, ref first); - TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); - Application.End (runstate); - } - - [Fact] - [AutoInitShutdown] - public void FileDialog_FileSystemWatcher () - { - for (int i = 0; i < 8; i++) { - var fd = new FileDialog (); - fd.Ready += (s, e) => Application.RequestStop (); - Application.Run (fd); + } else if (iterations > 0) { + RequestStop (); } + }; + + Begin (Top); + ((FakeDriver)Driver).SetBufferSize (20, 10); + Run (); + } + + [Fact] + [AutoInitShutdown] + public void ButtonAlignment_One () + { + var d = (FakeDriver)Driver; + RunState runstate = null; + + var title = "1234"; + // E.g "|[ ok ]|" + var btnText = "ok"; + var buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; + var width = buttonRow.Length; + + d.SetBufferSize (width, 1); + + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); + // Center + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Justify + buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btnText)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Right + buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btnText)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Left + buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btnText)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Wider + buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; + width = buttonRow.Length; + + d.SetBufferSize (width, 1); + + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Justify + buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btnText)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Right + buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btnText)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Left + buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btnText)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + } + + [Fact] + [AutoInitShutdown] + public void ButtonAlignment_Two () + { + RunState runstate = null; + + var d = (FakeDriver)Driver; + + var title = "1234"; + // E.g "|[ yes ][ no ]|" + var btn1Text = "yes"; + var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; + var btn2Text = "no"; + var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; + + var buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2} {CM.Glyphs.VLine}"; + var width = buttonRow.Length; + + d.SetBufferSize (buttonRow.Length, 3); + + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Justify + buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Right + buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Left + buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2} {CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + } + + [Fact] + [AutoInitShutdown] + public void ButtonAlignment_Two_Hidden () + { + RunState runstate = null; + var firstIteration = false; + + var d = (FakeDriver)Driver; + + var title = "1234"; + // E.g "|[ yes ][ no ]|" + var btn1Text = "yes"; + var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; + var btn2Text = "no"; + var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; + + var buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2} {CM.Glyphs.VLine}"; + var width = buttonRow.Length; + + d.SetBufferSize (buttonRow.Length, 3); + + Dialog dlg = null; + Button button1, button2; + + // Default (Center) + button1 = new Button (btn1Text); + button2 = new Button (btn2Text); + (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, button1, button2); + button1.Visible = false; + RunIteration (ref runstate, ref firstIteration); + buttonRow = $@"{CM.Glyphs.VLine} {btn2} {CM.Glyphs.VLine}"; + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Justify + Assert.Equal (width, buttonRow.Length); + button1 = new Button (btn1Text); + button2 = new Button (btn2Text); + (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, button1, button2); + button1.Visible = false; + RunIteration (ref runstate, ref firstIteration); + buttonRow = $@"{CM.Glyphs.VLine} {btn2}{CM.Glyphs.VLine}"; + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Right + Assert.Equal (width, buttonRow.Length); + button1 = new Button (btn1Text); + button2 = new Button (btn2Text); + (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, button1, button2); + button1.Visible = false; + RunIteration (ref runstate, ref firstIteration); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Left + Assert.Equal (width, buttonRow.Length); + button1 = new Button (btn1Text); + button2 = new Button (btn2Text); + (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, button1, button2); + button1.Visible = false; + RunIteration (ref runstate, ref firstIteration); + buttonRow = $@"{CM.Glyphs.VLine} {btn2} {CM.Glyphs.VLine}"; + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + } + + [Fact] + [AutoInitShutdown] + public void ButtonAlignment_Three () + { + RunState runstate = null; + + var d = (FakeDriver)Driver; + + var title = "1234"; + // E.g "|[ yes ][ no ][ maybe ]|" + var btn1Text = "yes"; + var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; + var btn2Text = "no"; + var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; + var btn3Text = "maybe"; + var btn3 = $"{CM.Glyphs.LeftBracket} {btn3Text} {CM.Glyphs.RightBracket}"; + + var buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {CM.Glyphs.VLine}"; + var width = buttonRow.Length; + + d.SetBufferSize (buttonRow.Length, 3); + + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Justify + buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2} {btn3}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Right + buttonRow = $@"{CM.Glyphs.VLine} {btn1} {btn2} {btn3}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Left + buttonRow = $@"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + } + + [Fact] + [AutoInitShutdown] + public void ButtonAlignment_Four () + { + RunState runstate = null; + + var d = (FakeDriver)Driver; + + var title = "1234"; + + // E.g "|[ yes ][ no ][ maybe ]|" + var btn1Text = "yes"; + var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; + var btn2Text = "no"; + var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; + var btn3Text = "maybe"; + var btn3 = $"{CM.Glyphs.LeftBracket} {btn3Text} {CM.Glyphs.RightBracket}"; + var btn4Text = "never"; + var btn4 = $"{CM.Glyphs.LeftBracket} {btn4Text} {CM.Glyphs.RightBracket}"; + + var buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; + var width = buttonRow.Length; + d.SetBufferSize (buttonRow.Length, 3); + + // Default - Center + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Justify + buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Right + buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Left + buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + } + + [Fact] + [AutoInitShutdown] + public void ButtonAlignment_Four_On_Too_Small_Width () + { + RunState runstate = null; + + var d = (FakeDriver)Driver; + + var title = "1234"; + + // E.g "|[ yes ][ no ][ maybe ][ never ]|" + var btn1Text = "yes"; + var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; + var btn2Text = "no"; + var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; + var btn3Text = "maybe"; + var btn3 = $"{CM.Glyphs.LeftBracket} {btn3Text} {CM.Glyphs.RightBracket}"; + var btn4Text = "never"; + var btn4 = $"{CM.Glyphs.LeftBracket} {btn4Text} {CM.Glyphs.RightBracket}"; + var buttonRow = string.Empty; + + var width = 30; + d.SetBufferSize (width, 1); + + // Default - Center + buttonRow = $"{CM.Glyphs.VLine}es {CM.Glyphs.RightBracket} {btn2} {btn3} {CM.Glyphs.LeftBracket} neve{CM.Glyphs.VLine}"; + (runstate, var dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + Assert.Equal (new Size (width, 1), dlg.Frame.Size); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Justify + buttonRow = + $"{CM.Glyphs.VLine}{CM.Glyphs.LeftBracket} yes {CM.Glyphs.LeftBracket} no {CM.Glyphs.LeftBracket} maybe {CM.Glyphs.LeftBracket} never {CM.Glyphs.RightBracket}{CM.Glyphs.VLine}"; + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Right + buttonRow = $"{CM.Glyphs.VLine}{CM.Glyphs.RightBracket} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Left + buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {CM.Glyphs.LeftBracket} n{CM.Glyphs.VLine}"; + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + } + + [Fact] + [AutoInitShutdown] + public void ButtonAlignment_Four_Wider () + { + RunState runstate = null; + + var d = (FakeDriver)Driver; + + var title = "1234"; + + // E.g "|[ yes ][ no ][ maybe ]|" + var btn1Text = "yes"; + var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; + var btn2Text = "no"; + var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; + var btn3Text = "你你你你你"; // This is a wide char + var btn3 = $"{CM.Glyphs.LeftBracket} {btn3Text} {CM.Glyphs.RightBracket}"; + // Requires a Nerd Font + var btn4Text = "\uE36E\uE36F\uE370\uE371\uE372\uE373"; + var btn4 = $"{CM.Glyphs.LeftBracket} {btn4Text} {CM.Glyphs.RightBracket}"; + + // Note extra spaces to make dialog even wider + // 123456 123456 + var buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; + var width = buttonRow.GetColumns (); + d.SetBufferSize (width, 3); + + // Default - Center + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Justify + buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.GetColumns ()); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Right + buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.GetColumns ()); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Left + buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.GetColumns ()); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + } + + [Fact] + [AutoInitShutdown] + public void ButtonAlignment_Four_WideOdd () + { + RunState runstate = null; + + var d = (FakeDriver)Driver; + + var title = "1234"; + + // E.g "|[ yes ][ no ][ maybe ]|" + var btn1Text = "really long button 1"; + var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; + var btn2Text = "really long button 2"; + var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; + var btn3Text = "really long button 3"; + var btn3 = $"{CM.Glyphs.LeftBracket} {btn3Text} {CM.Glyphs.RightBracket}"; + var btn4Text = "really long button 44"; // 44 is intentional to make length different than rest + var btn4 = $"{CM.Glyphs.LeftBracket} {btn4Text} {CM.Glyphs.RightBracket}"; + + // Note extra spaces to make dialog even wider + // 123456 1234567 + var buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; + var width = buttonRow.Length; + d.SetBufferSize (buttonRow.Length, 1); + + // Default - Center + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Justify + buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Right + buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {btn3} {btn4}{CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Left + buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {btn3} {btn4} {CM.Glyphs.VLine}"; + Assert.Equal (width, buttonRow.Length); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + } + + [Fact] + [AutoInitShutdown] + public void Zero_Buttons_Works () + { + RunState runstate = null; + + var d = (FakeDriver)Driver; + + var title = "1234"; + + var buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.VLine}"; + var width = buttonRow.Length; + d.SetBufferSize (buttonRow.Length, 3); + + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + + End (runstate); + } + + [Fact] + [AutoInitShutdown] + public void One_Button_Works () + { + RunState runstate = null; + + var d = (FakeDriver)Driver; + + var title = ""; + var btnText = "ok"; + var buttonRow = $"{CM.Glyphs.VLine} {CM.Glyphs.LeftBracket} {btnText} {CM.Glyphs.RightBracket} {CM.Glyphs.VLine}"; + + var width = buttonRow.Length; + d.SetBufferSize (buttonRow.Length, 10); + + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + } + + [Fact] + [AutoInitShutdown] + public void Add_Button_Works () + { + RunState runstate = null; + + var d = (FakeDriver)Driver; + + var title = "1234"; + var btn1Text = "yes"; + var btn1 = $"{CM.Glyphs.LeftBracket} {btn1Text} {CM.Glyphs.RightBracket}"; + var btn2Text = "no"; + var btn2 = $"{CM.Glyphs.LeftBracket} {btn2Text} {CM.Glyphs.RightBracket}"; + + // We test with one button first, but do this to get the width right for 2 + var width = $@"{CM.Glyphs.VLine} {btn1} {btn2} {CM.Glyphs.VLine}".Length; + d.SetBufferSize (width, 1); + + // Default (center) + var dlg = new Dialog (new Button (btn1Text)) { Title = title, Width = width, Height = 1, ButtonAlignment = Dialog.ButtonAlignments.Center }; + // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..) + dlg.Border.Thickness = new Thickness (1, 0, 1, 0); + runstate = Begin (dlg); + var buttonRow = $"{CM.Glyphs.VLine} {btn1} {CM.Glyphs.VLine}"; + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + + // Now add a second button + buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2} {CM.Glyphs.VLine}"; + dlg.AddButton (new Button (btn2Text)); + var first = false; + RunIteration (ref runstate, ref first); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Justify + dlg = new Dialog (new Button (btn1Text)) { Title = title, Width = width, Height = 1, ButtonAlignment = Dialog.ButtonAlignments.Justify }; + // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..) + dlg.Border.Thickness = new Thickness (1, 0, 1, 0); + runstate = Begin (dlg); + buttonRow = $"{CM.Glyphs.VLine} {btn1}{CM.Glyphs.VLine}"; + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + + // Now add a second button + buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2}{CM.Glyphs.VLine}"; + dlg.AddButton (new Button (btn2Text)); + first = false; + RunIteration (ref runstate, ref first); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Right + dlg = new Dialog (new Button (btn1Text)) { Title = title, Width = width, Height = 1, ButtonAlignment = Dialog.ButtonAlignments.Right }; + // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..) + dlg.Border.Thickness = new Thickness (1, 0, 1, 0); + runstate = Begin (dlg); + buttonRow = $"{CM.Glyphs.VLine}{new string (' ', width - btn1.Length - 2)}{btn1}{CM.Glyphs.VLine}"; + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + + // Now add a second button + buttonRow = $"{CM.Glyphs.VLine} {btn1} {btn2}{CM.Glyphs.VLine}"; + dlg.AddButton (new Button (btn2Text)); + first = false; + RunIteration (ref runstate, ref first); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + + // Left + dlg = new Dialog (new Button (btn1Text)) { Title = title, Width = width, Height = 1, ButtonAlignment = Dialog.ButtonAlignments.Left }; + // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..) + dlg.Border.Thickness = new Thickness (1, 0, 1, 0); + runstate = Begin (dlg); + buttonRow = $"{CM.Glyphs.VLine}{btn1}{new string (' ', width - btn1.Length - 2)}{CM.Glyphs.VLine}"; + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + + // Now add a second button + buttonRow = $"{CM.Glyphs.VLine}{btn1} {btn2} {CM.Glyphs.VLine}"; + dlg.AddButton (new Button (btn2Text)); + first = false; + RunIteration (ref runstate, ref first); + TestHelpers.AssertDriverContentsWithFrameAre ($"{buttonRow}", output); + End (runstate); + } + + [Fact] + [AutoInitShutdown] + public void FileDialog_FileSystemWatcher () + { + for (var i = 0; i < 8; i++) { + var fd = new FileDialog (); + fd.Ready += (s, e) => RequestStop (); + Run (fd); } + } - [Fact, AutoInitShutdown] - public void Dialog_Opened_From_Another_Dialog () - { - ((FakeDriver)Application.Driver).SetBufferSize (30, 10); + [Fact] [AutoInitShutdown] + public void Dialog_Opened_From_Another_Dialog () + { + ((FakeDriver)Driver).SetBufferSize (30, 10); - var btn1 = new Button ("press me 1"); - Button btn2 = null; - Button btn3 = null; - string expected = null; - btn1.Clicked += (s, e) => { - btn2 = new Button ("Show Sub"); - btn3 = new Button ("Close"); - btn3.Clicked += (s, e) => Application.RequestStop (); - btn2.Clicked += (s, e) => { - // Don't test MessageBox in Dialog unit tests! - var subBtn = new Button ("Ok") { IsDefault = true }; - var subDlg = new Dialog (subBtn) { Text = "ya", Width = 20, Height = 5 }; - subBtn.Clicked += (s, e) => Application.RequestStop (subDlg); - Application.Run (subDlg); - }; - var dlg = new Dialog (btn2, btn3); - - Application.Run (dlg); + var btn1 = new Button ("press me 1"); + Button btn2 = null; + Button btn3 = null; + string expected = null; + btn1.Clicked += (s, e) => { + btn2 = new Button ("Show Sub"); + btn3 = new Button ("Close"); + btn3.Clicked += (s, e) => RequestStop (); + btn2.Clicked += (s, e) => { + // Don't test MessageBox in Dialog unit tests! + var subBtn = new Button ("Ok") { IsDefault = true }; + var subDlg = new Dialog (subBtn) { Text = "ya", Width = 20, Height = 5 }; + subBtn.Clicked += (s, e) => RequestStop (subDlg); + Run (subDlg); }; - var btn = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Ok {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; + var dlg = new Dialog (btn2, btn3); - var iterations = -1; - Application.Iteration += (s, a) => { - iterations++; - if (iterations == 0) { - Assert.True (btn1.NewKeyDownEvent (new (KeyCode.Space))); - } else if (iterations == 1) { - expected = @$" + Run (dlg); + }; + var btn = $"{CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Ok {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}"; + + var iterations = -1; + Iteration += (s, a) => { + iterations++; + if (iterations == 0) { + Assert.True (btn1.NewKeyDownEvent (new Key (KeyCode.Space))); + } else if (iterations == 1) { + expected = @$" ┌───────────────────────┐ │ │ │ │ @@ -833,11 +802,11 @@ namespace Terminal.Gui.DialogTests { │ │ │{CM.Glyphs.LeftBracket} Show Sub {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Close {CM.Glyphs.RightBracket} │ └───────────────────────┘"; - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.True (btn2.NewKeyDownEvent (new (KeyCode.Space))); - } else if (iterations == 2) { - TestHelpers.AssertDriverContentsWithFrameAre (@$" + Assert.True (btn2.NewKeyDownEvent (new Key (KeyCode.Space))); + } else if (iterations == 2) { + TestHelpers.AssertDriverContentsWithFrameAre (@$" ┌───────────────────────┐ │ ┌──────────────────┐ │ │ │ya │ │ @@ -847,173 +816,196 @@ namespace Terminal.Gui.DialogTests { │{CM.Glyphs.LeftBracket} Show Sub {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Close {CM.Glyphs.RightBracket} │ └───────────────────────┘", output); - Assert.True (Application.Current.NewKeyDownEvent (new (KeyCode.Enter))); - } else if (iterations == 3) { - TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + Assert.True (Current.NewKeyDownEvent (new Key (KeyCode.Enter))); + } else if (iterations == 3) { + TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - Assert.True (btn3.NewKeyDownEvent (new (KeyCode.Space))); - } else if (iterations == 4) { - TestHelpers.AssertDriverContentsWithFrameAre ("", output); + Assert.True (btn3.NewKeyDownEvent (new Key (KeyCode.Space))); + } else if (iterations == 4) { + TestHelpers.AssertDriverContentsWithFrameAre ("", output); - Application.RequestStop (); - } - }; + RequestStop (); + } + }; - Application.Run (); - Application.Shutdown (); + Run (); + Shutdown (); - Assert.Equal (4, iterations); - } + Assert.Equal (4, iterations); + } - [Fact, AutoInitShutdown] - public void Dialog_In_Window_With_Size_One_Button_Aligns () - { - ((FakeDriver)Application.Driver).SetBufferSize (20, 5); + [Fact] [AutoInitShutdown] + public void Dialog_In_Window_With_Size_One_Button_Aligns () + { + ((FakeDriver)Driver).SetBufferSize (20, 5); - var win = new Window (); + var win = new Window (); - int iterations = 0; - Application.Iteration += (s, a) => { - if (++iterations > 2) { - Application.RequestStop (); - } - }; - var btn = $"{CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket}"; + var iterations = 0; + Iteration += (s, a) => { + if (++iterations > 2) { + RequestStop (); + } + }; + var btn = $"{CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket}"; - win.Loaded += (s, a) => { - var dlg = new Dialog (new Button ("Ok")) { Width = 18, Height = 3 }; + win.Loaded += (s, a) => { + var dlg = new Dialog (new Button ("Ok")) { Width = 18, Height = 3 }; - dlg.Loaded += (s, a) => { - Application.Refresh (); - var expected = @$" + dlg.Loaded += (s, a) => { + Refresh (); + var expected = @$" ┌──────────────────┐ │┌────────────────┐│ ││ {btn} ││ │└────────────────┘│ └──────────────────┘"; - _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - }; - - Application.Run (dlg); + _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); }; - Application.Run (win); - } - // [Theory, AutoInitShutdown] - // [InlineData (5)] - // //[InlineData (6)] - // //[InlineData (7)] - // //[InlineData (8)] - // //[InlineData (9)] - // public void Dialog_In_Window_Without_Size_One_Button_Aligns (int height) - // { - // ((FakeDriver)Application.Driver).SetBufferSize (20, height); - // var win = new Window (); + Run (dlg); + }; + Run (win); + } - // Application.Iteration += (s, a) => { - // var dlg = new Dialog ("Test", new Button ("Ok")); + [Theory] [AutoInitShutdown] + [InlineData (5, @" +┌┌───────────────┐─┐ +││ │ │ +││ ⟦ Ok ⟧ │ │ +│└───────────────┘ │ +└──────────────────┘")] + [InlineData (6, @" +┌┌───────────────┐─┐ +││ │ │ +││ │ │ +││ ⟦ Ok ⟧ │ │ +│└───────────────┘ │ +└──────────────────┘")] + [InlineData (7, @" +┌──────────────────┐ +│┌───────────────┐ │ +││ │ │ +││ │ │ +││ ⟦ Ok ⟧ │ │ +│└───────────────┘ │ +└──────────────────┘")] + [InlineData (8, @" +┌──────────────────┐ +│┌───────────────┐ │ +││ │ │ +││ │ │ +││ │ │ +││ ⟦ Ok ⟧ │ │ +│└───────────────┘ │ +└──────────────────┘")] + [InlineData (9, @" +┌──────────────────┐ +│┌───────────────┐ │ +││ │ │ +││ │ │ +││ │ │ +││ │ │ +││ ⟦ Ok ⟧ │ │ +│└───────────────┘ │ +└──────────────────┘")] + public void Dialog_In_Window_Without_Size_One_Button_Aligns (int height, string expected) + { + ((FakeDriver)Driver).SetBufferSize (20, height); + var win = new Window (); - // dlg.LayoutComplete += (s, a) => { - // Application.Refresh (); - // // BUGBUG: This seems wrong; is it a bug in Dim.Percent(85)?? - // var expected = @" - //┌┌┤Test├─────────┐─┐ - //││ │ │ - //││ [ Ok ] │ │ - //│└───────────────┘ │ - //└──────────────────┘"; - // _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + var iterations = -1; + Iteration += (s, a) => { + iterations++; + if (iterations == 0) { + var dlg = new Dialog (new Button ("Ok")); + Run (dlg); + } else if (iterations == 1) { + // BUGBUG: This seems wrong; is it a bug in Dim.Percent(85)?? No + _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + } else { + RequestStop (); + } + }; - // dlg.RequestStop (); - // win.RequestStop (); - // }; + Run (win); + } - // Application.Run (dlg); - // }; + // TODO: This is not really a Dialog test, but a ViewLayout test (Width = Dim.Fill (1) - Dim.Function (Btn_Width)) + // TODO: Move (and simplify) + [Fact] [AutoInitShutdown] + public void Dialog_In_Window_With_TextField_And_Button_AnchorEnd () + { + ((FakeDriver)Driver).SetBufferSize (20, 5); - // Application.Run (win); - // Application.Shutdown (); - // } + var win = new Window (); - // TODO: This is not really a Dialog test, but a ViewLayout test (Width = Dim.Fill (1) - Dim.Function (Btn_Width)) - // TODO: Move (and simplify) - [Fact, AutoInitShutdown] - public void Dialog_In_Window_With_TextField_And_Button_AnchorEnd () - { - ((FakeDriver)Application.Driver).SetBufferSize (20, 5); + var iterations = 0; + Iteration += (s, a) => { + if (++iterations > 2) { + RequestStop (); + } + }; + var b = $"{CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket}"; - var win = new Window (); + win.Loaded += (s, a) => { + var dlg = new Dialog { Width = 18, Height = 3 }; + Assert.Equal (16, dlg.Bounds.Width); - int iterations = 0; - Application.Iteration += (s, a) => { - if (++iterations > 2) { - Application.RequestStop (); - } - }; - var b = $"{CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket}"; + Button btn = null; + btn = new Button ("Ok") { + X = Pos.AnchorEnd () - Pos.Function (Btn_Width) + }; + btn.SetRelativeLayout (dlg.Bounds); + Assert.Equal (6, btn.Bounds.Width); + Assert.Equal (10, btn.Frame.X); // dlg.Bounds.Width (16) - btn.Frame.Width (6) = 10 + Assert.Equal (0, btn.Frame.Y); + Assert.Equal (6, btn.Frame.Width); + Assert.Equal (1, btn.Frame.Height); + int Btn_Width () => btn?.Bounds.Width ?? 0; + var tf = new TextField ("01234567890123456789") { + // Dim.Fill (1) fills remaining space minus 1 + // Dim.Function (Btn_Width) is 6 + Width = Dim.Fill (1) - Dim.Function (Btn_Width) + }; + tf.SetRelativeLayout (dlg.Bounds); + Assert.Equal (9, tf.Bounds.Width); // dlg.Bounds.Width (16) - Dim.Fill (1) - Dim.Function (6) = 9 + Assert.Equal (0, tf.Frame.X); + Assert.Equal (0, tf.Frame.Y); + Assert.Equal (9, tf.Frame.Width); + Assert.Equal (1, tf.Frame.Height); - win.Loaded += (s, a) => { - var dlg = new Dialog () { Width = 18, Height = 3 }; - Assert.Equal (16, dlg.Bounds.Width); + dlg.Loaded += (s, a) => { + Refresh (); + Assert.Equal (new Rect (10, 0, 6, 1), btn.Frame); + Assert.Equal (new Rect (0, 0, 6, 1), btn.Bounds); - Button btn = null; - btn = new Button ("Ok") { - X = Pos.AnchorEnd () - Pos.Function (Btn_Width) - }; - btn.SetRelativeLayout (dlg.Bounds); - Assert.Equal (6, btn.Bounds.Width); - Assert.Equal (10, btn.Frame.X); // dlg.Bounds.Width (16) - btn.Frame.Width (6) = 10 - Assert.Equal (0, btn.Frame.Y); - Assert.Equal (6, btn.Frame.Width); - Assert.Equal (1, btn.Frame.Height); - int Btn_Width () - { - return (btn?.Bounds.Width) ?? 0; - } - var tf = new TextField ("01234567890123456789") { - // Dim.Fill (1) fills remaining space minus 1 - // Dim.Function (Btn_Width) is 6 - Width = Dim.Fill (1) - Dim.Function (Btn_Width) - }; - tf.SetRelativeLayout (dlg.Bounds); - Assert.Equal (9, tf.Bounds.Width); // dlg.Bounds.Width (16) - Dim.Fill (1) - Dim.Function (6) = 9 - Assert.Equal (0, tf.Frame.X); - Assert.Equal (0, tf.Frame.Y); - Assert.Equal (9, tf.Frame.Width); - Assert.Equal (1, tf.Frame.Height); - - dlg.Loaded += (s, a) => { - Application.Refresh (); - Assert.Equal (new Rect (10, 0, 6, 1), btn.Frame); - Assert.Equal (new Rect (0, 0, 6, 1), btn.Bounds); - - var expected = @$" + var expected = @" ┌──────────────────┐ │┌────────────────┐│ ││012345678 ⟦ Ok ⟧││ │└────────────────┘│ └──────────────────┘"; - _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); + _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - dlg.SetNeedsLayout (); - dlg.LayoutSubviews (); - Application.Refresh (); - Assert.Equal (new Rect (10, 0, 6, 1), btn.Frame); - Assert.Equal (new Rect (0, 0, 6, 1), btn.Bounds); - expected = @$" + dlg.SetNeedsLayout (); + dlg.LayoutSubviews (); + Refresh (); + Assert.Equal (new Rect (10, 0, 6, 1), btn.Frame); + Assert.Equal (new Rect (0, 0, 6, 1), btn.Bounds); + expected = @" ┌──────────────────┐ │┌────────────────┐│ ││012345678 ⟦ Ok ⟧││ │└────────────────┘│ └──────────────────┘"; - _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); - }; - dlg.Add (btn, tf); - - Application.Run (dlg); + _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output); }; - Application.Run (win); - } + dlg.Add (btn, tf); + + Run (dlg); + }; + Run (win); } } \ No newline at end of file diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs index e310f5d7b..f3c6e3df9 100644 --- a/UnitTests/Text/TextFormatterTests.cs +++ b/UnitTests/Text/TextFormatterTests.cs @@ -1,1770 +1,1851 @@ -using System.Text; -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Text; using Xunit; using Xunit.Abstractions; - // Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; -namespace Terminal.Gui.TextTests { - public class TextFormatterTests { - readonly ITestOutputHelper output; +namespace Terminal.Gui.TextTests; - public TextFormatterTests (ITestOutputHelper output) - { - this.output = output; - } +public class TextFormatterTests { + readonly ITestOutputHelper output; - [Fact] - public void Basic_Usage () - { - var testText = "test"; - var expectedSize = new Size (); - var testBounds = new Rect (0, 0, 100, 1); - var tf = new TextFormatter (); + public TextFormatterTests (ITestOutputHelper output) => this.output = output; - tf.Text = testText; - expectedSize = new Size (testText.Length, 1); - Assert.Equal (testText, tf.Text); - Assert.Equal (TextAlignment.Left, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.Lines); - - tf.Alignment = TextAlignment.Right; - expectedSize = new Size (testText.Length, 1); - Assert.Equal (testText, tf.Text); - Assert.Equal (TextAlignment.Right, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.Lines); - - tf.Alignment = TextAlignment.Right; - expectedSize = new Size (testText.Length * 2, 1); - tf.Size = expectedSize; - Assert.Equal (testText, tf.Text); - Assert.Equal (TextAlignment.Right, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.Lines); - - tf.Alignment = TextAlignment.Centered; - expectedSize = new Size (testText.Length * 2, 1); - tf.Size = expectedSize; - Assert.Equal (testText, tf.Text); - Assert.Equal (TextAlignment.Centered, tf.Alignment); - Assert.Equal (expectedSize, tf.Size); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.Equal (expectedSize, tf.Size); - Assert.NotEmpty (tf.Lines); - } - - [Theory] - [InlineData (TextDirection.LeftRight_TopBottom, false)] - [InlineData (TextDirection.LeftRight_TopBottom, true)] - [InlineData (TextDirection.TopBottom_LeftRight, false)] - [InlineData (TextDirection.TopBottom_LeftRight, true)] - public void TestSize_TextChange (TextDirection textDirection, bool autoSize) - { - var tf = new TextFormatter () { Direction = textDirection, Text = "你", AutoSize = autoSize }; - Assert.Equal (2, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - tf.Text = "你你"; - if (autoSize) { - if (textDirection == TextDirection.LeftRight_TopBottom) { - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } else { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (2, tf.Size.Height); - } - } else { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } - } - - [Theory] - [InlineData (TextDirection.LeftRight_TopBottom)] - [InlineData (TextDirection.TopBottom_LeftRight)] - public void TestSize_AutoSizeChange (TextDirection textDirection) - { - var tf = new TextFormatter () { Direction = textDirection, Text = "你你" }; - if (textDirection == TextDirection.LeftRight_TopBottom) { - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } else { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (2, tf.Size.Height); - } - Assert.False (tf.AutoSize); - - tf.Size = new Size (1, 1); - Assert.Equal (1, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - tf.AutoSize = true; - if (textDirection == TextDirection.LeftRight_TopBottom) { - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } else { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (2, tf.Size.Height); - } - } - - [Theory] - [InlineData (TextDirection.LeftRight_TopBottom, false)] - [InlineData (TextDirection.LeftRight_TopBottom, true)] - [InlineData (TextDirection.TopBottom_LeftRight, false)] - [InlineData (TextDirection.TopBottom_LeftRight, true)] - public void TestSize_SizeChange_AutoSize_True_Or_False (TextDirection textDirection, bool autoSize) - { - var tf = new TextFormatter () { Direction = textDirection, Text = "你你", AutoSize = autoSize }; - if (textDirection == TextDirection.LeftRight_TopBottom) { - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } else { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (2, tf.Size.Height); - } - - tf.Size = new Size (1, 1); - if (autoSize) { - if (textDirection == TextDirection.LeftRight_TopBottom) { - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } else { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (2, tf.Size.Height); - } - } else { - Assert.Equal (1, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } - } - - [Theory] - [InlineData (TextAlignment.Left, false)] - [InlineData (TextAlignment.Centered, true)] - [InlineData (TextAlignment.Right, false)] - [InlineData (TextAlignment.Justified, true)] - public void TestSize_SizeChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize) - { - var tf = new TextFormatter () { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize }; - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - - tf.Size = new Size (1, 1); - if (autoSize && textAlignment != TextAlignment.Justified) { - Assert.Equal (4, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } else { - Assert.Equal (1, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } - } - - [Theory] - [InlineData (VerticalTextAlignment.Top, false)] - [InlineData (VerticalTextAlignment.Middle, true)] - [InlineData (VerticalTextAlignment.Bottom, false)] - [InlineData (VerticalTextAlignment.Justified, true)] - public void TestSize_SizeChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize) - { - var tf = new TextFormatter () { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize }; - Assert.Equal (2, tf.Size.Width); - Assert.Equal (2, tf.Size.Height); - - tf.Size = new Size (1, 1); - if (autoSize && textAlignment != VerticalTextAlignment.Justified) { - Assert.Equal (2, tf.Size.Width); - Assert.Equal (2, tf.Size.Height); - } else { - Assert.Equal (1, tf.Size.Width); - Assert.Equal (1, tf.Size.Height); - } - } - - [Fact] - public void NeedsFormat_Sets () - { - var testText = "test"; - var testBounds = new Rect (0, 0, 100, 1); - var tf = new TextFormatter (); - - tf.Text = "test"; - Assert.True (tf.NeedsFormat); // get_Lines causes a Format - Assert.NotEmpty (tf.Lines); - Assert.False (tf.NeedsFormat); // get_Lines causes a Format - Assert.Equal (testText, tf.Text); - tf.Draw (testBounds, new Attribute (), new Attribute ()); - Assert.False (tf.NeedsFormat); - - tf.Size = new Size (1, 1); - Assert.True (tf.NeedsFormat); - Assert.NotEmpty (tf.Lines); - Assert.False (tf.NeedsFormat); // get_Lines causes a Format - - tf.Alignment = TextAlignment.Centered; - Assert.True (tf.NeedsFormat); - Assert.NotEmpty (tf.Lines); - Assert.False (tf.NeedsFormat); // get_Lines causes a Format - } - - [Theory] - [InlineData (null)] - [InlineData ("")] - [InlineData ("no hotkey")] - [InlineData ("No hotkey, Upper Case")] - [InlineData ("Non-english: Сохранить")] - public void FindHotKey_Invalid_ReturnsFalse (string text) - { - Rune hotKeySpecifier = (Rune)'_'; - bool supportFirstUpperCase = false; - int hotPos = 0; - Key hotKey = KeyCode.Null; - bool result = false; - - result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out hotPos, out hotKey); - Assert.False (result); - Assert.Equal (-1, hotPos); - Assert.Equal (KeyCode.Null, 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) - { - Rune hotKeySpecifier = (Rune)'_'; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey); - if (expectedResult) { - Assert.True (result); - } else { - Assert.False (result); - } - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal ((Key)expectedKey, hotKey); - } - - [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) - { - Rune hotKeySpecifier = (Rune)'_'; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey); - if (expectedResult) { - Assert.True (result); - } else { - Assert.False (result); - } - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal ((Key)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) - { - Rune hotKeySpecifier = (Rune)'_'; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey); - if (expectedResult) { - Assert.True (result); - } else { - Assert.False (result); - } - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal ((Key)expectedKey, 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; - - Rune hotKeySpecifier = (Rune)0; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey); - if (expectedResult) { - Assert.True (result); - } else { - Assert.False (result); - } - Assert.Equal (expectedResult, result); - Assert.Equal (expectedHotPos, hotPos); - Assert.Equal ((Key)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)'_'; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out int _, out var hotKey); - Assert.Equal (found, result); - Assert.Equal ((Key)expected, 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) - { - bool supportFirstUpperCase = true; - - var hotKeySpecifier = (Rune)0; - - var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey); - Assert.False (result); - Assert.Equal (-1, hotPos); - Assert.Equal (KeyCode.Null, hotKey); - } - - [Theory] - [InlineData (null)] - [InlineData ("")] - [InlineData ("a")] - public void RemoveHotKeySpecifier_InValid_ReturnsOriginal (string text) - { - Rune 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 ("_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) - { - Rune hotKeySpecifier = (Rune)'_'; - - Assert.Equal (expectedText, TextFormatter.RemoveHotKeySpecifier (text, hotPos, 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) - { - Rune hotKeySpecifier = (Rune)'_'; - - Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier)); - } - - [Theory] - [InlineData (null)] - [InlineData ("")] - public void CalcRect_Invalid_Returns_Empty (string text) - { - Assert.Equal (Rect.Empty, TextFormatter.CalcRect (0, 0, text)); - Assert.Equal (new Rect (new Point (1, 2), Size.Empty), TextFormatter.CalcRect (1, 2, text)); - Assert.Equal (new Rect (new Point (-1, -2), Size.Empty), TextFormatter.CalcRect (-1, -2, text)); - } - - [Theory] - [InlineData ("test")] - [InlineData (" ~  s  gui.cs   master ↑10")] - public void CalcRect_SingleLine_Returns_1High (string text) - { - Assert.Equal (new Rect (0, 0, text.GetRuneCount (), 1), TextFormatter.CalcRect (0, 0, text)); - Assert.Equal (new Rect (0, 0, text.GetColumns (), 1), TextFormatter.CalcRect (0, 0, text)); - } - - [Theory] - [InlineData ("line1\nline2", 5, 2)] - [InlineData ("\nline2", 5, 2)] - [InlineData ("\n\n", 0, 3)] - [InlineData ("\n\n\n", 0, 4)] - [InlineData ("line1\nline2\nline3long!", 10, 3)] - [InlineData ("line1\nline2\n\n", 5, 4)] - [InlineData ("line1\r\nline2", 5, 2)] - [InlineData (" ~  s  gui.cs   master ↑10\n", 31, 2)] - [InlineData ("\n ~  s  gui.cs   master ↑10", 31, 2)] - [InlineData (" ~  s  gui.cs   master\n↑10", 27, 2)] - public void CalcRect_MultiLine_Returns_nHigh (string text, int expectedWidth, int expectedLines) - { - Assert.Equal (new Rect (0, 0, expectedWidth, expectedLines), TextFormatter.CalcRect (0, 0, text)); - var lines = text.Split (text.Contains (Environment.NewLine) ? Environment.NewLine : "\n"); - var maxWidth = lines.Max (s => s.GetColumns ()); - var lineWider = 0; - for (int i = 0; i < lines.Length; i++) { - var w = lines [i].GetColumns (); - if (w == maxWidth) { - lineWider = i; - } - } - Assert.Equal (new Rect (0, 0, maxWidth, expectedLines), TextFormatter.CalcRect (0, 0, text)); - Assert.Equal (new Rect (0, 0, lines [lineWider].ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 0)), expectedLines), TextFormatter.CalcRect (0, 0, text)); - } - - [Theory] - [InlineData ("")] - [InlineData (null)] - [InlineData ("test")] - public void ClipAndJustify_Invalid_Returns_Original (string text) - { - var expected = string.IsNullOrEmpty (text) ? text : ""; - Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left)); - Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left)); - Assert.Throws (() => TextFormatter.ClipAndJustify (text, -1, TextAlignment.Left)); - } - - [Theory] - [InlineData ("test", "", 0)] - [InlineData ("test", "te", 2)] - [InlineData ("test", "test", int.MaxValue)] - [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit - [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit - [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit - [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] - [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] - [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] - [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] - [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit - [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit - public void ClipAndJustify_Valid_Left (string text, string justifiedText, int maxWidth) - { - var align = TextAlignment.Left; - var textDirection = TextDirection.LeftRight_BottomTop; - var tabWidth = 1; - - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - Assert.True (justifiedText.GetRuneCount () <= maxWidth); - Assert.True (justifiedText.GetColumns () <= maxWidth); - Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); - Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); - Assert.True (expectedClippedWidth <= maxWidth); - Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText); - } - - [Theory] - [InlineData ("test", "", 0)] - [InlineData ("test", "te", 2)] - [InlineData ("test", "test", int.MaxValue)] - [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit - [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit - [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit - [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] - [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] - [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] - [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] - [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit - [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit - public void ClipAndJustify_Valid_Right (string text, string justifiedText, int maxWidth) - { - var align = TextAlignment.Right; - var textDirection = TextDirection.LeftRight_BottomTop; - var tabWidth = 1; - - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - Assert.True (justifiedText.GetRuneCount () <= maxWidth); - Assert.True (justifiedText.GetColumns () <= maxWidth); - Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); - Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); - Assert.True (expectedClippedWidth <= maxWidth); - Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText); - } - - [Theory] - [InlineData ("test", "", 0)] - [InlineData ("test", "te", 2)] - [InlineData ("test", "test", int.MaxValue)] - [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit - [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit - [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit - [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] - [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] - [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] - [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] - [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit - [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit - public void ClipAndJustify_Valid_Centered (string text, string justifiedText, int maxWidth) - { - var align = TextAlignment.Centered; - var textDirection = TextDirection.LeftRight_TopBottom; - var tabWidth = 1; - - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - Assert.True (justifiedText.GetRuneCount () <= maxWidth); - Assert.True (justifiedText.GetColumns () <= maxWidth); - Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); - Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); - Assert.True (expectedClippedWidth <= maxWidth); - Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText); - } - - [Theory] - [InlineData ("test", "", 0)] - [InlineData ("test", "te", 2)] - [InlineData ("test", "test", int.MaxValue)] // This doesn't throw because it only create a word with length 1 - [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit - [InlineData ("A sentence has words.", "A sentence has words.", 500)] // should fit - [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit - [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit - // Now throw System.OutOfMemoryException. See https://stackoverflow.com/questions/20672920/maxcapacity-of-stringbuilder - //[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] - [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] - [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] // This doesn't throw because it only create a line with length 1 - [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] - [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit - [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit - [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit - public void ClipAndJustify_Valid_Justified (string text, string justifiedText, int maxWidth) - { - var align = TextAlignment.Justified; - var textDirection = TextDirection.LeftRight_TopBottom; - var tabWidth = 1; - - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); - Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); - Assert.True (justifiedText.GetRuneCount () <= maxWidth); - Assert.True (justifiedText.GetColumns () <= maxWidth); - Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); - Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); - Assert.True (expectedClippedWidth <= maxWidth); - Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText); - - // see Justify_ tests below - } - - [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] - [InlineData ("word")] // Even # of chars - [InlineData ("word.")] // Odd # of chars - [InlineData ("пÑивеÑ")] // Unicode (even #) - [InlineData ("пÑивеÑ.")] // Unicode (odd # of chars) - public void Justify_SingleWord (string text) - { - var justifiedText = text; - char 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] - // 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) - { - char 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 == ' ')); - } - - [Fact] - public void WordWrap_Invalid () - { - var text = string.Empty; - int width = 0; - - Assert.Empty (TextFormatter.WordWrapText (null, width)); - Assert.Empty (TextFormatter.WordWrapText (text, width)); - Assert.Throws (() => TextFormatter.WordWrapText (text, -1)); - } - - [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]); - } - - [Theory] - [InlineData ("Constantinople", 14, 0, new string [] { "Constantinople" })] - [InlineData ("Constantinople", 12, -2, new string [] { "Constantinop", "le" })] - [InlineData ("Constantinople", 9, -5, new string [] { "Constanti", "nople" })] - [InlineData ("Constantinople", 7, -7, new string [] { "Constan", "tinople" })] - [InlineData ("Constantinople", 5, -9, new string [] { "Const", "antin", "ople" })] - [InlineData ("Constantinople", 4, -10, new string [] { "Cons", "tant", "inop", "le" })] - [InlineData ("Constantinople", 1, -13, new string [] { "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); - var 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 string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 50, -1, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 46, -5, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 26, -25, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 17, -34, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 13, -38, new string [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" })] - [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 1, -50, new string [] { "ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ", "ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร", "ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ", "อ", "ฮ", "ฯ", "ะั", "า", "ำ" })] - public void WordWrap_Unicode_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable resultLines) - { - List wrappedLines; - - var zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0); - Assert.Single (zeroWidth); - Assert.Equal ('ั', zeroWidth.ElementAt (0).Value); - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var 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); - } - - [Theory] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 19, 0, new string [] { "This\u00A0is\u00A0a\u00A0sentence." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 18, -1, new string [] { "This\u00A0is\u00A0a\u00A0sentence", "." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 17, -2, new string [] { "This\u00A0is\u00A0a\u00A0sentenc", "e." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 14, -5, new string [] { "This\u00A0is\u00A0a\u00A0sent", "ence." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 10, -9, new string [] { "This\u00A0is\u00A0a\u00A0", "sentence." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 7, -12, new string [] { "This\u00A0is", "\u00A0a\u00A0sent", "ence." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 5, -14, new string [] { "This\u00A0", "is\u00A0a\u00A0", "sente", "nce." })] - [InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 1, -18, new string [] { "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); - var 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 string [] { "This\u00A0is\u00A0a\u00A0sentence." })] - [InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 19, -1, new string [] { "This\u00A0is\u00A0a\u00A0sentence." })] - [InlineData ("\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence.", 19, 0, new string [] { "\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); - var 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 string [] { "A sentence has words." })] - [InlineData ("A sentence has words.", 20, -1, new string [] { "A sentence has", "words." })] - [InlineData ("A sentence has words.", 15, -6, new string [] { "A sentence has", "words." })] - [InlineData ("A sentence has words.", 14, -7, new string [] { "A sentence has", "words." })] - [InlineData ("A sentence has words.", 13, -8, new string [] { "A sentence", "has words." })] - // Unicode - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 42, 0, new string [] { "A Unicode sentence (пÑивеÑ) has words." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 41, -1, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 36, -6, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 35, -7, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 34, -8, new string [] { "A Unicode sentence (пÑивеÑ)", "has words." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.", 25, -17, new string [] { "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); - var 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); - } - - /// - /// WordWrap strips CRLF - /// - [Theory] - [InlineData ("A sentence has words.\nA paragraph has lines.", 44, 0, new string [] { "A sentence has words.A paragraph has lines." })] - [InlineData ("A sentence has words.\nA paragraph has lines.", 43, -1, new string [] { "A sentence has words.A paragraph has lines." })] - [InlineData ("A sentence has words.\nA paragraph has lines.", 38, -6, new string [] { "A sentence has words.A paragraph has", "lines." })] - [InlineData ("A sentence has words.\nA paragraph has lines.", 34, -10, new string [] { "A sentence has words.A paragraph", "has lines." })] - [InlineData ("A sentence has words.\nA paragraph has lines.", 27, -17, new string [] { "A sentence has words.A", "paragraph has lines." })] - // Unicode - [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 69, 0, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 68, -1, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 63, -6, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has", "Линии." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 59, -10, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт", "has Линии." })] - [InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 52, -17, new string [] { "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); - var 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.", 3, -18, new string [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] - [InlineData ("A sentence has words.", 2, -19, new string [] { "A", "se", "nt", "en", "ce", "ha", "s", "wo", "rd", "s." })] - [InlineData ("A sentence has words.", 1, -20, new string [] { "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); - var 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.", 14, -7, new string [] { "A sentence ", "has words." })] - [InlineData ("A sentence has words.", 8, -13, new string [] { "A ", "sentence", " has ", "words." })] - [InlineData ("A sentence has words.", 6, -15, new string [] { "A ", "senten", "ce ", "has ", "words." })] - [InlineData ("A sentence has words.", 3, -18, new string [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds." })] - [InlineData ("A sentence has words.", 2, -19, new string [] { "A ", "se", "nt", "en", "ce", " ", "ha", "s ", "wo", "rd", "s." })] - [InlineData ("A sentence has words.", 1, -20, new string [] { "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); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: 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 string [] { "文に は言葉 ", "があり ます。" })] - [InlineData ("文に は言葉 があり ます。", 3, -11, new string [] { "文", "に ", "は", "言", "葉 ", "が", "あ", "り ", "ま", "す", "。" })] - [InlineData ("文に は言葉 があり ます。", 2, -12, new string [] { "文", "に", " ", "は", "言", "葉", " ", "が", "あ", "り", " ", "ま", "す", "。" })] - [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); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: 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 string [] { "文に は言葉", "があり ます。" })] - [InlineData ("文に は言葉 があり ます。", 3, -11, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })] - [InlineData ("文に は言葉 があり ます。", 2, -12, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })] - [InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { " ", " ", " " })] // 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); - var 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. ", 3, new string [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds.", " " })] - [InlineData ("A sentence has words. ", 3, new string [] { "A ", " ", "sen", "ten", "ce ", " ", " ", " ", "has", " ", "wor", "ds.", " " })] - public void WordWrap_PreserveTrailingSpaces_True_With_Simple_Runes_Width_3 (string text, int width, IEnumerable resultLines) - { - var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - foreach (var line in wrappedLines) { - breakLines += $"{line}{Environment.NewLine}"; - } - var expected = string.Empty; - foreach (var 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 (null, 1, new string [] { })] // null input - [InlineData ("", 1, new string [] { })] // Empty input - [InlineData ("1 34", 1, new string [] { "1", "3", "4" })] // Single Spaces - [InlineData ("1", 1, new string [] { "1" })] // Short input - [InlineData ("12", 1, new string [] { "1", "2" })] - [InlineData ("123", 1, new string [] { "1", "2", "3" })] - [InlineData ("123456", 1, new string [] { "1", "2", "3", "4", "5", "6" })] // No spaces - [InlineData (" ", 1, new string [] { " " })] // Just Spaces; should result in a single space - [InlineData (" ", 1, new string [] { " " })] - [InlineData (" ", 1, new string [] { " ", " " })] - [InlineData (" ", 1, new string [] { " ", " " })] - [InlineData ("12 456", 1, new string [] { "1", "2", "4", "5", "6" })] // Single Spaces - [InlineData (" 2 456", 1, new string [] { " ", "2", "4", "5", "6" })] // Leading spaces should be preserved. - [InlineData (" 2 456 8", 1, new string [] { " ", "2", "4", "5", "6", "8" })] - [InlineData ("A sentence has words. ", 1, new string [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })] // Complex example - [InlineData ("12 567", 1, new string [] { "1", "2", " ", "5", "6", "7" })] // Double Spaces - [InlineData (" 3 567", 1, new string [] { " ", "3", "5", "6", "7" })] // Double Leading spaces should be preserved. - [InlineData (" 3 678 1", 1, new string [] { " ", "3", " ", "6", "7", "8", " ", "1" })] - [InlineData ("1 456", 1, new string [] { "1", " ", "4", "5", "6" })] - [InlineData ("A sentence has words. ", 1, new string [] { "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) - { - var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - foreach (var line in wrappedLines) { - breakLines += $"{line}{Environment.NewLine}"; - } - var expected = string.Empty; - foreach (var 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 string [] { "1" })] // Short input - [InlineData ("12", 3, new string [] { "12" })] - [InlineData ("123", 3, new string [] { "123" })] - [InlineData ("123456", 3, new string [] { "123", "456" })] // No spaces - [InlineData ("1234567", 3, new string [] { "123", "456", "7" })] // No spaces - [InlineData (" ", 3, new string [] { " " })] // Just Spaces; should result in a single space - [InlineData (" ", 3, new string [] { " " })] - [InlineData (" ", 3, new string [] { " " })] - [InlineData (" ", 3, new string [] { " " })] - [InlineData ("12 456", 3, new string [] { "12", "456" })] // Single Spaces - [InlineData (" 2 456", 3, new string [] { " 2", "456" })] // Leading spaces should be preserved. - [InlineData (" 2 456 8", 3, new string [] { " 2", "456", "8" })] - [InlineData ("A sentence has words. ", 3, new string [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] // Complex example - [InlineData ("12 567", 3, new string [] { "12 ", "567" })] // Double Spaces - [InlineData (" 3 567", 3, new string [] { " 3", "567" })] // Double Leading spaces should be preserved. - [InlineData (" 3 678 1", 3, new string [] { " 3", " 67", "8 ", "1" })] - [InlineData ("1 456", 3, new string [] { "1 ", "456" })] - [InlineData ("A sentence has words. ", 3, new string [] { "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) - { - var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - foreach (var line in wrappedLines) { - breakLines += $"{line}{Environment.NewLine}"; - } - var expected = string.Empty; - foreach (var 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 string [] { "1" })] // Short input - [InlineData ("12", 50, new string [] { "12" })] - [InlineData ("123", 50, new string [] { "123" })] - [InlineData ("123456", 50, new string [] { "123456" })] // No spaces - [InlineData ("1234567", 50, new string [] { "1234567" })] // No spaces - [InlineData (" ", 50, new string [] { " " })] // Just Spaces; should result in a single space - [InlineData (" ", 50, new string [] { " " })] - [InlineData (" ", 50, new string [] { " " })] - [InlineData ("12 456", 50, new string [] { "12 456" })] // Single Spaces - [InlineData (" 2 456", 50, new string [] { " 2 456" })] // Leading spaces should be preserved. - [InlineData (" 2 456 8", 50, new string [] { " 2 456 8" })] - [InlineData ("A sentence has words. ", 50, new string [] { "A sentence has words. " })] // Complex example - [InlineData ("12 567", 50, new string [] { "12 567" })] // Double Spaces - [InlineData (" 3 567", 50, new string [] { " 3 567" })] // Double Leading spaces should be preserved. - [InlineData (" 3 678 1", 50, new string [] { " 3 678 1" })] - [InlineData ("1 456", 50, new string [] { "1 456" })] - [InlineData ("A sentence has words. ", 50, new string [] { "A sentence has words. " })] // Double space Complex example - public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_50 (string text, int width, IEnumerable resultLines) - { - var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false); - Assert.Equal (wrappedLines.Count, resultLines.Count ()); - Assert.Equal (resultLines, wrappedLines); - var breakLines = ""; - foreach (var line in wrappedLines) { - breakLines += $"{line}{Environment.NewLine}"; - } - var expected = string.Empty; - foreach (var line in resultLines) { - expected += $"{line}{Environment.NewLine}"; - } - Assert.Equal (expected, breakLines); - } - - [Theory] - [InlineData ("A sentence\t\t\t has words.", 14, -10, new string [] { "A sentence\t", "\t\t has ", "words." })] - [InlineData ("A sentence\t\t\t has words.", 8, -16, new string [] { "A ", "sentence", "\t\t", "\t ", "has ", "words." })] - [InlineData ("A sentence\t\t\t has words.", 3, -21, new string [] { "A ", "sen", "ten", "ce", "\t", "\t", "\t", " ", "has", " ", "wor", "ds." })] - [InlineData ("A sentence\t\t\t has words.", 2, -22, new string [] { "A ", "se", "nt", "en", "ce", "\t", "\t", "\t", " ", "ha", "s ", "wo", "rd", "s." })] - [InlineData ("A sentence\t\t\t has words.", 1, -23, new string [] { "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); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: true, tabWidth: 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 ("これが最初の行です。 こんにちは世界。 これが2行目です。", 29, 0, new string [] { "これが最初の行です。", "こんにちは世界。", "これが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); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: false); - 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 ("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 (); - var runes = text.ToRuneList (); - Rune rune; - if (Rune.TryGetRuneAt (text, hotPos, out rune)) { - Assert.Equal (rune, (Rune)tag); - - } - var result = tf.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] - [InlineData ("", -1, TextAlignment.Left, false, 0)] - [InlineData (null, 0, TextAlignment.Left, false, 1)] - [InlineData (null, 0, TextAlignment.Left, true, 1)] - [InlineData ("", 0, TextAlignment.Left, false, 1)] - [InlineData ("", 0, TextAlignment.Left, true, 1)] - public void Reformat_Invalid (string text, int maxWidth, TextAlignment textAlignment, bool wrap, int linesCount) - { - if (maxWidth < 0) { - Assert.Throws (() => TextFormatter.Format (text, maxWidth, textAlignment, wrap)); - } else { - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap); - Assert.NotEmpty (list); - Assert.True (list.Count == linesCount); - Assert.Equal (string.Empty, list [0]); - } - } - - [Theory] - [InlineData ("", 0, 0, TextAlignment.Left, false, 1, true)] - [InlineData ("", 1, 1, TextAlignment.Left, false, 1, true)] - [InlineData ("A sentence has words.", 0, -21, TextAlignment.Left, false, 1, true)] - [InlineData ("A sentence has words.", 1, -20, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.", 5, -16, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.", 20, -1, TextAlignment.Left, false, 1, false)] - // no clip - [InlineData ("A sentence has words.", 21, 0, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.", 22, 1, TextAlignment.Left, false, 1, false)] - public void Reformat_NoWordrap_SingleLine (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - var list = TextFormatter.Format (text, maxWidth, textAlignment, 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 () [0..expectedClippedWidth]), list [0]); - } - - [Theory] - [InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true)] - [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 1, false)] - // no clip - [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true)] - [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 1, false, 1)] - [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 1, false)] - [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 1, false)] - public void Reformat_NoWordrap_NewLines_MultiLine_False (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, int clipWidthOffset = 0) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset; - var list = TextFormatter.Format (text, maxWidth, textAlignment, 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 () [0..expectedClippedWidth]).Replace ("\r\n", " "), list [0]); - } else if (text.Contains ('\n') && maxWidth > 0) { - Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]).Replace ("\n", " "), list [0]); - } else { - Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]), list [0]); - } - } - - [Theory] - [InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new string [] { "" })] - [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })] - [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })] - [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - //// no clip - [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new string [] { "" })] - [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })] - [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })] - [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - public void Reformat_NoWordrap_NewLines_MultiLine_True (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, IEnumerable resultLines) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var list = TextFormatter.Format (text, maxWidth, textAlignment, 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, TextAlignment.Left, false, 1, true, new string [] { "" })] - [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })] - [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })] - [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - //// no clip - [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new string [] { "" })] - [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })] - [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })] - [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })] - public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, IEnumerable resultLines) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var list = TextFormatter.Format (text, maxWidth, textAlignment, 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] - // Even # of spaces - // 0123456789 - [InlineData ("012 456 89", 0, -10, TextAlignment.Left, true, true, true, new string [] { "" })] - [InlineData ("012 456 89", 1, -9, TextAlignment.Left, true, true, false, new string [] { "0", "1", "2", " ", "4", "5", "6", " ", "8", "9" }, "01245689")] - [InlineData ("012 456 89", 5, -5, TextAlignment.Left, true, true, false, new string [] { "012 ", "456 ", "89" })] - [InlineData ("012 456 89", 9, -1, TextAlignment.Left, true, true, false, new string [] { "012 456 ", "89" })] - // no clip - [InlineData ("012 456 89", 10, 0, TextAlignment.Left, true, true, false, new string [] { "012 456 89" })] - [InlineData ("012 456 89", 11, 1, TextAlignment.Left, true, true, false, new string [] { "012 456 89" })] - // Odd # of spaces - // 01234567890123 - [InlineData ("012 456 89 end", 13, -1, TextAlignment.Left, true, true, false, new string [] { "012 456 89 ", "end" })] - // no clip - [InlineData ("012 456 89 end", 14, 0, TextAlignment.Left, true, true, false, new string [] { "012 456 89 end" })] - [InlineData ("012 456 89 end", 15, 1, TextAlignment.Left, true, true, false, new string [] { "012 456 89 end" })] - public void Reformat_Wrap_Spaces_No_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, bool stringEmpty, IEnumerable resultLines, string noSpaceText = "") - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); - var list = TextFormatter.Format (text, maxWidth, textAlignment, 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, TextAlignment.Left, wrap, preserveTrailingSpaces: false); - 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 () [0..expectedClippedWidth]), list [0]); - } - } - } - - [Theory] - // Unicode - // Even # of chars - // 0123456789 - [InlineData ("\u2660пÑРвРÑ", 10, -1, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвÐ", "Ñ" })] - // no clip - [InlineData ("\u2660пÑРвРÑ", 11, 0, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвРÑ" })] - [InlineData ("\u2660пÑРвРÑ", 12, 1, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвРÑ" })] - // Unicode - // Odd # of chars - // 0123456789 - [InlineData ("\u2660 ÑРвРÑ", 9, -1, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвÐ", "Ñ" })] - // no clip - [InlineData ("\u2660 ÑРвРÑ", 10, 0, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвРÑ" })] - [InlineData ("\u2660 ÑРвРÑ", 11, 1, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвРÑ" })] - public void Reformat_Unicode_Wrap_Spaces_No_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable resultLines) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); - Assert.Equal (list.Count, resultLines.Count ()); - Assert.Equal (resultLines, list); - } - - [Theory] - // Unicode - [InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 8, -1, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })] - // no clip - [InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 9, 0, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })] - [InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 10, 1, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })] - public void Reformat_Unicode_Wrap_Spaces_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable resultLines) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); - Assert.Equal (list.Count, resultLines.Count ()); - Assert.Equal (resultLines, list); - } - - [Theory] - [InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, false, new string [] { " 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, TextAlignment.Left, true, true, new string [] { " 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, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable resultLines, string expectedWrappedText) - { - Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); - var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); - Assert.Equal (list.Count, resultLines.Count ()); - Assert.Equal (resultLines, list); - string wrappedText = string.Empty; - foreach (var txt in list) wrappedText += txt; - Assert.Equal (expectedWrappedText, wrappedText); - } - - [Fact] - public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_End_Spaces_As_True () - { - var exception = Record.Exception (() => TextFormatter.Format ("Some text", 4, TextAlignment.Left, 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, TextAlignment.Justified, false, true) [0]; - Assert.Equal (i, fmtText.GetRuneCount ()); - var 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, TextAlignment.Justified, false, true, 0, TextDirection.TopBottom_LeftRight) [0]; - Assert.Equal (i, fmtText.GetRuneCount ()); - var c = fmtText [^1]; - Assert.True (text.EndsWith (c)); - } - Assert.Equal (justifiedText, fmtText); - } - - [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 ("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)); - } - - [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 ("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", 11, 6, 1, 1)] - [InlineData ("こんにちは 世界", 15, 6, 1, 2)] - public void GetSumMaxCharWidth_Simple_And_Wide_Runes (string text, int width, int index, int length, int indexWidth) - { - Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text)); - Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text, index, length)); - } - - [Theory] - [InlineData (new string [] { "Hello", "World" }, 2, 1, 1, 1)] - [InlineData (new string [] { "こんにちは", "世界" }, 4, 1, 1, 2)] - public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes (IEnumerable text, int width, int index, int length, int indexWidth) - { - Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text.ToList ())); - Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text.ToList (), index, length)); - } - - [Theory] - [InlineData ("test", 3, 3)] - [InlineData ("test", 4, 4)] - [InlineData ("test", 10, 4)] - public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength) - { - var runes = text.ToRuneList (); - - Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, 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)); - } - - [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 ("Hello World", 6, 6)] - [InlineData ("こんにちは 世界", 6, 3)] - [MemberData (nameof (CMGlyphs))] - public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength) - { - var runes = text.ToRuneList (); - Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns)); - } - - public static IEnumerable CMGlyphs => - new List - { - new object[] { $"{CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}", 16, 15 } - }; - - [Theory] - [InlineData ("Truncate", 3, "Tru")] - [InlineData ("デモエムポンズ", 3, "デ")] - public void Format_Truncate_Simple_And_Wide_Runes (string text, int width, string expected) - { - var 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; - var 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); - } - - public static IEnumerable FormatEnvironmentNewLine => - new List - { - new object[] { $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}", 60, new string [] { "Line1", "Line2", "Line3" } } - }; - - [Theory] - [MemberData (nameof (SplitEnvironmentNewLine))] - public void SplitNewLine_Ending__With_Or_Without_NewLine_Probably_CRLF (string text, IEnumerable expected) - { - var splited = TextFormatter.SplitNewLine (text); - Assert.Equal (expected, splited); - } - - public static IEnumerable SplitEnvironmentNewLine => - new List - { - new object[] { $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界", new string [] { "First Line 界", "Second Line 界", "Third Line 界" } }, - new object[] { $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界{Environment.NewLine}", new string [] { "First Line 界", "Second Line 界", "Third Line 界", "" } } + public static IEnumerable CMGlyphs => + new List { + new object [] { $"{CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}", 16, 15 } }; - [Theory] - [InlineData ($"First Line 界\nSecond Line 界\nThird Line 界", new string [] { "First Line 界", "Second Line 界", "Third Line 界" })] - public void SplitNewLine_Ending_Without_NewLine_Only_LF (string text, IEnumerable expected) - { - var splited = TextFormatter.SplitNewLine (text); - Assert.Equal (expected, splited); - } + public static IEnumerable FormatEnvironmentNewLine => + new List { + new object [] { $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}", 60, new [] { "Line1", "Line2", "Line3" } } + }; - [Theory] - [InlineData ($"First Line 界\nSecond Line 界\nThird Line 界\n", new string [] { "First Line 界", "Second Line 界", "Third Line 界", "" })] - public void SplitNewLine_Ending_With_NewLine_Only_LF (string text, IEnumerable expected) - { - var splited = TextFormatter.SplitNewLine (text); - Assert.Equal (expected, splited); - } + 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] - [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.MaxWidthLine (text)); - } + [Fact] + public void Basic_Usage () + { + var testText = "test"; + var expectedSize = new Size (); + var testBounds = new Rect (0, 0, 100, 1); + var tf = new TextFormatter (); - [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) - { - var 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 string (new char [] { text [index], text [index + 1] })); + tf.Text = testText; + expectedSize = new Size (testText.Length, 1); + Assert.Equal (testText, tf.Text); + Assert.Equal (TextAlignment.Left, tf.Alignment); + Assert.Equal (expectedSize, tf.Size); + tf.Draw (testBounds, new Attribute (), new Attribute ()); + Assert.Equal (expectedSize, tf.Size); + Assert.NotEmpty (tf.Lines); + + tf.Alignment = TextAlignment.Right; + expectedSize = new Size (testText.Length, 1); + Assert.Equal (testText, tf.Text); + Assert.Equal (TextAlignment.Right, tf.Alignment); + Assert.Equal (expectedSize, tf.Size); + tf.Draw (testBounds, new Attribute (), new Attribute ()); + Assert.Equal (expectedSize, tf.Size); + Assert.NotEmpty (tf.Lines); + + tf.Alignment = TextAlignment.Right; + expectedSize = new Size (testText.Length * 2, 1); + tf.Size = expectedSize; + Assert.Equal (testText, tf.Text); + Assert.Equal (TextAlignment.Right, tf.Alignment); + Assert.Equal (expectedSize, tf.Size); + tf.Draw (testBounds, new Attribute (), new Attribute ()); + Assert.Equal (expectedSize, tf.Size); + Assert.NotEmpty (tf.Lines); + + tf.Alignment = TextAlignment.Centered; + expectedSize = new Size (testText.Length * 2, 1); + tf.Size = expectedSize; + Assert.Equal (testText, tf.Text); + Assert.Equal (TextAlignment.Centered, tf.Alignment); + Assert.Equal (expectedSize, tf.Size); + tf.Draw (testBounds, new Attribute (), new Attribute ()); + Assert.Equal (expectedSize, tf.Size); + Assert.NotEmpty (tf.Lines); + } + + [Theory] + [InlineData (TextDirection.LeftRight_TopBottom, false)] + [InlineData (TextDirection.LeftRight_TopBottom, true)] + [InlineData (TextDirection.TopBottom_LeftRight, false)] + [InlineData (TextDirection.TopBottom_LeftRight, true)] + public void TestSize_TextChange (TextDirection textDirection, bool autoSize) + { + var tf = new TextFormatter { Direction = textDirection, Text = "你", AutoSize = autoSize }; + Assert.Equal (2, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + tf.Text = "你你"; + if (autoSize) { + if (textDirection == TextDirection.LeftRight_TopBottom) { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); } else { - // Rune array length is equal to string array - Assert.Equal (expected, text [index].ToString ()); + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + } + } else { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } + } + + [Theory] + [InlineData (TextDirection.LeftRight_TopBottom)] + [InlineData (TextDirection.TopBottom_LeftRight)] + public void TestSize_AutoSizeChange (TextDirection textDirection) + { + var tf = new TextFormatter { Direction = textDirection, Text = "你你" }; + if (textDirection == TextDirection.LeftRight_TopBottom) { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } else { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + } + Assert.False (tf.AutoSize); + + tf.Size = new Size (1, 1); + Assert.Equal (1, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + tf.AutoSize = true; + if (textDirection == TextDirection.LeftRight_TopBottom) { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } else { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + } + } + + [Theory] + [InlineData (TextDirection.LeftRight_TopBottom, false)] + [InlineData (TextDirection.LeftRight_TopBottom, true)] + [InlineData (TextDirection.TopBottom_LeftRight, false)] + [InlineData (TextDirection.TopBottom_LeftRight, true)] + public void TestSize_SizeChange_AutoSize_True_Or_False (TextDirection textDirection, bool autoSize) + { + var tf = new TextFormatter { Direction = textDirection, Text = "你你", AutoSize = autoSize }; + if (textDirection == TextDirection.LeftRight_TopBottom) { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } else { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + } + + tf.Size = new Size (1, 1); + if (autoSize) { + if (textDirection == TextDirection.LeftRight_TopBottom) { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } else { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + } + } else { + Assert.Equal (1, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } + } + + [Theory] + [InlineData (TextAlignment.Left, false)] + [InlineData (TextAlignment.Centered, true)] + [InlineData (TextAlignment.Right, false)] + [InlineData (TextAlignment.Justified, true)] + public void TestSize_SizeChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize) + { + var tf = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize }; + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + + tf.Size = new Size (1, 1); + if (autoSize && textAlignment != TextAlignment.Justified) { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } else { + Assert.Equal (1, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } + } + + [Theory] + [InlineData (VerticalTextAlignment.Top, false)] + [InlineData (VerticalTextAlignment.Middle, true)] + [InlineData (VerticalTextAlignment.Bottom, false)] + [InlineData (VerticalTextAlignment.Justified, true)] + public void TestSize_SizeChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize) + { + var tf = new TextFormatter { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize }; + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + + tf.Size = new Size (1, 1); + if (autoSize && textAlignment != VerticalTextAlignment.Justified) { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + } else { + Assert.Equal (1, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } + } + + [Theory] + [InlineData (TextAlignment.Left, false)] + [InlineData (TextAlignment.Centered, true)] + [InlineData (TextAlignment.Right, false)] + [InlineData (TextAlignment.Justified, true)] + public void TestSize_DirectionChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize) + { + var tf = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize }; + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + + tf.Direction = TextDirection.TopBottom_LeftRight; + if (autoSize && textAlignment != TextAlignment.Justified) { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + } else { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } + } + + [Theory] + [InlineData (VerticalTextAlignment.Top, false)] + [InlineData (VerticalTextAlignment.Middle, true)] + [InlineData (VerticalTextAlignment.Bottom, false)] + [InlineData (VerticalTextAlignment.Justified, true)] + public void TestSize_DirectionChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize) + { + var tf = new TextFormatter { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize }; + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + + tf.Direction = TextDirection.LeftRight_TopBottom; + if (autoSize && textAlignment != VerticalTextAlignment.Justified) { + Assert.Equal (4, tf.Size.Width); + Assert.Equal (1, tf.Size.Height); + } else { + Assert.Equal (2, tf.Size.Width); + Assert.Equal (2, tf.Size.Height); + } + } + + [Fact] + public void NeedsFormat_Sets () + { + var testText = "test"; + var testBounds = new Rect (0, 0, 100, 1); + var tf = new TextFormatter (); + + tf.Text = "test"; + Assert.True (tf.NeedsFormat); // get_Lines causes a Format + Assert.NotEmpty (tf.Lines); + Assert.False (tf.NeedsFormat); // get_Lines causes a Format + Assert.Equal (testText, tf.Text); + tf.Draw (testBounds, new Attribute (), new Attribute ()); + Assert.False (tf.NeedsFormat); + + tf.Size = new Size (1, 1); + Assert.True (tf.NeedsFormat); + Assert.NotEmpty (tf.Lines); + Assert.False (tf.NeedsFormat); // get_Lines causes a Format + + tf.Alignment = TextAlignment.Centered; + Assert.True (tf.NeedsFormat); + Assert.NotEmpty (tf.Lines); + Assert.False (tf.NeedsFormat); // get_Lines causes a Format + } + + [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, supportFirstUpperCase, out hotPos, out hotKey); + Assert.False (result); + Assert.Equal (-1, hotPos); + Assert.Equal (KeyCode.Null, 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)'_'; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + 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')] // 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)'_'; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + 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)'_'; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + 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 ("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; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + 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)'_'; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out var _, out var hotKey); + Assert.Equal (found, result); + Assert.Equal (expected, 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; + + var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey); + Assert.False (result); + Assert.Equal (-1, hotPos); + Assert.Equal (KeyCode.Null, hotKey); + } + + [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 ("_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 ("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 (null)] + [InlineData ("")] + public void CalcRect_Invalid_Returns_Empty (string text) + { + Assert.Equal (Rect.Empty, TextFormatter.CalcRect (0, 0, text)); + Assert.Equal (new Rect (new Point (1, 2), Size.Empty), TextFormatter.CalcRect (1, 2, text)); + Assert.Equal (new Rect (new Point (-1, -2), Size.Empty), TextFormatter.CalcRect (-1, -2, text)); + } + + [Theory] + [InlineData ("test")] + [InlineData (" ~  s  gui.cs   master ↑10")] + public void CalcRect_SingleLine_Returns_1High (string text) + { + Assert.Equal (new Rect (0, 0, text.GetRuneCount (), 1), TextFormatter.CalcRect (0, 0, text)); + Assert.Equal (new Rect (0, 0, text.GetColumns (), 1), TextFormatter.CalcRect (0, 0, text)); + } + + [Theory] + [InlineData ("line1\nline2", 5, 2)] + [InlineData ("\nline2", 5, 2)] + [InlineData ("\n\n", 0, 3)] + [InlineData ("\n\n\n", 0, 4)] + [InlineData ("line1\nline2\nline3long!", 10, 3)] + [InlineData ("line1\nline2\n\n", 5, 4)] + [InlineData ("line1\r\nline2", 5, 2)] + [InlineData (" ~  s  gui.cs   master ↑10\n", 31, 2)] + [InlineData ("\n ~  s  gui.cs   master ↑10", 31, 2)] + [InlineData (" ~  s  gui.cs   master\n↑10", 27, 2)] + public void CalcRect_MultiLine_Returns_nHigh (string text, int expectedWidth, int expectedLines) + { + Assert.Equal (new Rect (0, 0, expectedWidth, expectedLines), TextFormatter.CalcRect (0, 0, text)); + var lines = text.Split (text.Contains (Environment.NewLine) ? Environment.NewLine : "\n"); + var maxWidth = lines.Max (s => s.GetColumns ()); + var lineWider = 0; + for (var i = 0; i < lines.Length; i++) { + var w = lines [i].GetColumns (); + if (w == maxWidth) { + lineWider = i; } } + Assert.Equal (new Rect (0, 0, maxWidth, expectedLines), TextFormatter.CalcRect (0, 0, text)); + Assert.Equal (new Rect (0, 0, lines [lineWider].ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 0)), expectedLines), TextFormatter.CalcRect (0, 0, text)); + } - [Fact] - public void GetLengthThatFits_With_Combining_Runes () - { - var text = "Les Mise\u0328\u0301rables"; - Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14)); + [Theory] + [InlineData ("")] + [InlineData (null)] + [InlineData ("test")] + public void ClipAndJustify_Invalid_Returns_Original (string text) + { + var expected = string.IsNullOrEmpty (text) ? text : ""; + Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left)); + Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left)); + Assert.Throws (() => TextFormatter.ClipAndJustify (text, -1, TextAlignment.Left)); + } + + [Theory] + [InlineData ("test", "", 0)] + [InlineData ("test", "te", 2)] + [InlineData ("test", "test", int.MaxValue)] + [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit + [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit + [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit + [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] + [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] + [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] + [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] + [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit + [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit + public void ClipAndJustify_Valid_Left (string text, string justifiedText, int maxWidth) + { + var align = TextAlignment.Left; + var textDirection = TextDirection.LeftRight_BottomTop; + var tabWidth = 1; + + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + Assert.True (justifiedText.GetRuneCount () <= maxWidth); + Assert.True (justifiedText.GetColumns () <= maxWidth); + Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); + Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); + Assert.True (expectedClippedWidth <= maxWidth); + Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText); + } + + [Theory] + [InlineData ("test", "", 0)] + [InlineData ("test", "te", 2)] + [InlineData ("test", "test", int.MaxValue)] + [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit + [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit + [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit + [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] + [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] + [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] + [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] + [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit + [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit + public void ClipAndJustify_Valid_Right (string text, string justifiedText, int maxWidth) + { + var align = TextAlignment.Right; + var textDirection = TextDirection.LeftRight_BottomTop; + var tabWidth = 1; + + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + Assert.True (justifiedText.GetRuneCount () <= maxWidth); + Assert.True (justifiedText.GetColumns () <= maxWidth); + Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); + Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); + Assert.True (expectedClippedWidth <= maxWidth); + Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText); + } + + [Theory] + [InlineData ("test", "", 0)] + [InlineData ("test", "te", 2)] + [InlineData ("test", "test", int.MaxValue)] + [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit + [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit + [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit + [InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] + [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] + [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] + [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] + [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit + [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit + public void ClipAndJustify_Valid_Centered (string text, string justifiedText, int maxWidth) + { + var align = TextAlignment.Centered; + var textDirection = TextDirection.LeftRight_TopBottom; + var tabWidth = 1; + + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + Assert.True (justifiedText.GetRuneCount () <= maxWidth); + Assert.True (justifiedText.GetColumns () <= maxWidth); + Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); + Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); + Assert.True (expectedClippedWidth <= maxWidth); + Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText); + } + + [Theory] + [InlineData ("test", "", 0)] + [InlineData ("test", "te", 2)] + [InlineData ("test", "test", int.MaxValue)] // This doesn't throw because it only create a word with length 1 + [InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit + [InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit + [InlineData ("A sentence has words.", + "A sentence has words.", + 500)] // should fit + [InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit + [InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit + // Now throw System.OutOfMemoryException. See https://stackoverflow.com/questions/20672920/maxcapacity-of-stringbuilder + //[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)] + [InlineData ("A\tsentence\thas\twords.", "A sentence", 10)] + [InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] // This doesn't throw because it only create a line with length 1 + [InlineData ("line1\nline2\nline3long!", "line1\nline", 10)] + [InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit + [InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit + [InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit + public void ClipAndJustify_Valid_Justified (string text, string justifiedText, int maxWidth) + { + var align = TextAlignment.Justified; + var textDirection = TextDirection.LeftRight_TopBottom; + var tabWidth = 1; + + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth); + Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth)); + Assert.True (justifiedText.GetRuneCount () <= maxWidth); + Assert.True (justifiedText.GetColumns () <= maxWidth); + Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ()); + Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1))); + Assert.True (expectedClippedWidth <= maxWidth); + Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText); + + // see Justify_ tests below + } + + [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] + [InlineData ("word")] // Even # of chars + [InlineData ("word.")] // Odd # of chars + [InlineData ("пÑивеÑ")] // Unicode (even #) + [InlineData ("пÑивеÑ.")] // Unicode (odd # of chars) + public void Justify_SingleWord (string text) + { + var justifiedText = text; + var fillChar = '+'; + + var 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] + // 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 == ' ')); + } + + [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)); + } + + [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]); + } + + [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); + var 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; + + var zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0); + Assert.Single (zeroWidth); + Assert.Equal ('ั', zeroWidth.ElementAt (0).Value); + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var 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); + } + + [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); + var 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); + var 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); + var 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); + } + + /// + /// 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); + var 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.", 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); + var 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.", 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); + var 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); + var 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 [] { " ", " ", " " })] // 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); + var 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. ", 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) + { + var wrappedLines = TextFormatter.WordWrapText (text, width, true); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + foreach (var line in wrappedLines) { + breakLines += $"{line}{Environment.NewLine}"; + } + var expected = string.Empty; + foreach (var 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 (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) + { + var wrappedLines = TextFormatter.WordWrapText (text, width); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + foreach (var line in wrappedLines) { + breakLines += $"{line}{Environment.NewLine}"; + } + var expected = string.Empty; + foreach (var 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) + { + var wrappedLines = TextFormatter.WordWrapText (text, width); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + foreach (var line in wrappedLines) { + breakLines += $"{line}{Environment.NewLine}"; + } + var expected = string.Empty; + foreach (var 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) + { + var wrappedLines = TextFormatter.WordWrapText (text, width); + Assert.Equal (wrappedLines.Count, resultLines.Count ()); + Assert.Equal (resultLines, wrappedLines); + var breakLines = ""; + foreach (var line in wrappedLines) { + breakLines += $"{line}{Environment.NewLine}"; + } + var expected = string.Empty; + foreach (var line in resultLines) { + expected += $"{line}{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); + var 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 ("これが最初の行です。 こんにちは世界。 これが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); + var 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 ("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 (); + var runes = text.ToRuneList (); + Rune rune; + if (Rune.TryGetRuneAt (text, hotPos, out rune)) { + Assert.Equal (rune, (Rune)tag); + + } + var result = tf.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] + [InlineData ("", -1, TextAlignment.Left, false, 0)] + [InlineData (null, 0, TextAlignment.Left, false, 1)] + [InlineData (null, 0, TextAlignment.Left, true, 1)] + [InlineData ("", 0, TextAlignment.Left, false, 1)] + [InlineData ("", 0, TextAlignment.Left, true, 1)] + public void Reformat_Invalid (string text, int maxWidth, TextAlignment textAlignment, bool wrap, int linesCount) + { + if (maxWidth < 0) { + Assert.Throws (() => TextFormatter.Format (text, maxWidth, textAlignment, wrap)); + } else { + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap); + Assert.NotEmpty (list); + Assert.True (list.Count == linesCount); + Assert.Equal (string.Empty, list [0]); + } + } + + [Theory] + [InlineData ("", 0, 0, TextAlignment.Left, false, 1, true)] + [InlineData ("", 1, 1, TextAlignment.Left, false, 1, true)] + [InlineData ("A sentence has words.", 0, -21, TextAlignment.Left, false, 1, true)] + [InlineData ("A sentence has words.", 1, -20, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.", 5, -16, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.", 20, -1, TextAlignment.Left, false, 1, false)] + // no clip + [InlineData ("A sentence has words.", 21, 0, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.", 22, 1, TextAlignment.Left, false, 1, false)] + public void Reformat_NoWordrap_SingleLine (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + var list = TextFormatter.Format (text, maxWidth, textAlignment, 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] + [InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true)] + [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 1, false)] + // no clip + [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true)] + [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 1, false, 1)] + [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 1, false)] + [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 1, false)] + public void Reformat_NoWordrap_NewLines_MultiLine_False (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + int linesCount, + bool stringEmpty, + int clipWidthOffset = 0) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset; + var list = TextFormatter.Format (text, maxWidth, textAlignment, 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, TextAlignment.Left, false, 1, true, new [] { "" })] + [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new [] { "A", "L" })] + [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })] + [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + //// no clip + [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new [] { "" })] + [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new [] { "A", "L" })] + [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })] + [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + public void Reformat_NoWordrap_NewLines_MultiLine_True (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + int linesCount, + bool stringEmpty, + IEnumerable resultLines) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var list = TextFormatter.Format (text, maxWidth, textAlignment, 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]); } - [Fact] - public void GetMaxColsForWidth_With_Combining_Runes () - { - var text = new List () { "Les Mis", "e\u0328\u0301", "rables" }; - Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1)); + Assert.Equal (list, resultLines); + } + + [Theory] + [InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new [] { "" })] + [InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new [] { "A", "L" })] + [InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })] + [InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + //// no clip + [InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new [] { "" })] + [InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new [] { "A", "L" })] + [InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })] + [InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + [InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })] + public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + int linesCount, + bool stringEmpty, + IEnumerable resultLines) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var list = TextFormatter.Format (text, maxWidth, textAlignment, 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]); } - [Fact] - public void GetSumMaxCharWidth_With_Combining_Runes () - { - var text = "Les Mise\u0328\u0301rables"; - Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1)); - } + Assert.Equal (list, resultLines); + } - [Fact] - public void GetSumMaxCharWidth_List_With_Combining_Runes () - { - var text = new List () { "Les Mis", "e\u0328\u0301", "rables" }; - Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1)); + [Theory] + // Even # of spaces + // 0123456789 + [InlineData ("012 456 89", 0, -10, TextAlignment.Left, true, true, true, new [] { "" })] + [InlineData ("012 456 89", 1, -9, TextAlignment.Left, true, true, false, new [] { "0", "1", "2", " ", "4", "5", "6", " ", "8", "9" }, "01245689")] + [InlineData ("012 456 89", 5, -5, TextAlignment.Left, true, true, false, new [] { "012 ", "456 ", "89" })] + [InlineData ("012 456 89", 9, -1, TextAlignment.Left, true, true, false, new [] { "012 456 ", "89" })] + // no clip + [InlineData ("012 456 89", 10, 0, TextAlignment.Left, true, true, false, new [] { "012 456 89" })] + [InlineData ("012 456 89", 11, 1, TextAlignment.Left, true, true, false, new [] { "012 456 89" })] + // Odd # of spaces + // 01234567890123 + [InlineData ("012 456 89 end", 13, -1, TextAlignment.Left, true, true, false, new [] { "012 456 89 ", "end" })] + // no clip + [InlineData ("012 456 89 end", 14, 0, TextAlignment.Left, true, true, false, new [] { "012 456 89 end" })] + [InlineData ("012 456 89 end", 15, 1, TextAlignment.Left, true, true, false, new [] { "012 456 89 end" })] + public void Reformat_Wrap_Spaces_No_NewLines (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + bool preserveTrailingSpaces, + bool stringEmpty, + IEnumerable resultLines, + string noSpaceText = "") + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); + var list = TextFormatter.Format (text, maxWidth, textAlignment, 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); - [Theory] - [InlineData (14, 1, TextDirection.LeftRight_TopBottom)] - [InlineData (1, 14, TextDirection.TopBottom_LeftRight)] - public void CalcRect_With_Combining_Runes (int width, int height, TextDirection textDirection) - { - var text = "Les Mise\u0328\u0301rables"; - Assert.Equal (new Rect (0, 0, width, height), TextFormatter.CalcRect (0, 0, text, textDirection)); + 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, TextAlignment.Left, 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 (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, @" + [Theory] + // Unicode + // Even # of chars + // 0123456789 + [InlineData ("\u2660пÑРвРÑ", 10, -1, TextAlignment.Left, true, false, new [] { "\u2660пÑРвÐ", "Ñ" })] + // no clip + [InlineData ("\u2660пÑРвРÑ", 11, 0, TextAlignment.Left, true, false, new [] { "\u2660пÑРвРÑ" })] + [InlineData ("\u2660пÑРвРÑ", 12, 1, TextAlignment.Left, true, false, new [] { "\u2660пÑРвРÑ" })] + // Unicode + // Odd # of chars + // 0123456789 + [InlineData ("\u2660 ÑРвРÑ", 9, -1, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвÐ", "Ñ" })] + // no clip + [InlineData ("\u2660 ÑРвРÑ", 10, 0, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвРÑ" })] + [InlineData ("\u2660 ÑРвРÑ", 11, 1, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвРÑ" })] + public void Reformat_Unicode_Wrap_Spaces_No_NewLines (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + bool preserveTrailingSpaces, + IEnumerable resultLines) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); + Assert.Equal (list.Count, resultLines.Count ()); + Assert.Equal (resultLines, list); + } + + [Theory] + // Unicode + [InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 8, -1, TextAlignment.Left, 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, TextAlignment.Left, true, false, new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })] + [InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 10, 1, TextAlignment.Left, true, false, new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })] + public void Reformat_Unicode_Wrap_Spaces_NewLines (string text, + int maxWidth, + int widthOffset, + TextAlignment textAlignment, + bool wrap, + bool preserveTrailingSpaces, + IEnumerable resultLines) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); + Assert.Equal (list.Count, resultLines.Count ()); + Assert.Equal (resultLines, list); + } + + [Theory] + [InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, 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, TextAlignment.Left, 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, + TextAlignment textAlignment, + bool wrap, + bool preserveTrailingSpaces, + IEnumerable resultLines, + string expectedWrappedText) + { + Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); + var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces); + Assert.Equal (list.Count, resultLines.Count ()); + Assert.Equal (resultLines, list); + var wrappedText = string.Empty; + foreach (var txt in list) { + wrappedText += txt; + } + Assert.Equal (expectedWrappedText, wrappedText); + } + + [Fact] + public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_End_Spaces_As_True () + { + var exception = Record.Exception (() => TextFormatter.Format ("Some text", 4, TextAlignment.Left, 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 (var i = text.GetRuneCount (); i < maxWidth; i++) { + fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true) [0]; + Assert.Equal (i, fmtText.GetRuneCount ()); + var 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 (var i = text.GetRuneCount (); i < maxWidth; i++) { + fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true, 0, TextDirection.TopBottom_LeftRight) [0]; + Assert.Equal (i, fmtText.GetRuneCount ()); + var c = fmtText [^1]; + Assert.True (text.EndsWith (c)); + } + Assert.Equal (justifiedText, fmtText); + } + + [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 ("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)); + + [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 ("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", 11, 6, 1, 1)] + [InlineData ("こんにちは 世界", 15, 6, 1, 2)] + public void GetSumMaxCharWidth_Simple_And_Wide_Runes (string text, int width, int index, int length, int indexWidth) + { + Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text)); + Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text, index, length)); + } + + [Theory] + [InlineData (new [] { "Hello", "World" }, 2, 1, 1, 1)] + [InlineData (new [] { "こんにちは", "世界" }, 4, 1, 1, 2)] + public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes (IEnumerable text, int width, int index, int length, int indexWidth) + { + Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text.ToList ())); + Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text.ToList (), index, length)); + } + + [Theory] + [InlineData ("test", 3, 3)] + [InlineData ("test", 4, 4)] + [InlineData ("test", 10, 4)] + public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength) + { + var runes = text.ToRuneList (); + + Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, 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)); + + [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 ("Hello World", 6, 6)] + [InlineData ("こんにちは 世界", 6, 3)] + [MemberData (nameof (CMGlyphs))] + public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength) + { + var runes = text.ToRuneList (); + Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns)); + } + + [Theory] + [InlineData ("Truncate", 3, "Tru")] + [InlineData ("デモエムポンズ", 3, "デ")] + public void Format_Truncate_Simple_And_Wide_Runes (string text, int width, string expected) + { + var 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; + var 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] + [MemberData (nameof (SplitEnvironmentNewLine))] + public void SplitNewLine_Ending__With_Or_Without_NewLine_Probably_CRLF (string text, IEnumerable expected) + { + var 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) + { + var 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) + { + var splited = TextFormatter.SplitNewLine (text); + Assert.Equal (expected, splited); + } + + [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.MaxWidthLine (text)); + + [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) + { + var 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 string (new [] { text [index], text [index + 1] })); + } else { + // Rune array length is equal to string array + Assert.Equal (expected, text [index].ToString ()); + } + } + + [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 () + { + var text = new List { "Les Mis", "e\u0328\u0301", "rables" }; + Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1)); + } + + [Fact] + public void GetSumMaxCharWidth_With_Combining_Runes () + { + var text = "Les Mise\u0328\u0301rables"; + Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1)); + } + + [Fact] + public void GetSumMaxCharWidth_List_With_Combining_Runes () + { + var text = new List { "Les Mis", "e\u0328\u0301", "rables" }; + Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1)); + } + + [Theory] + [InlineData (14, 1, TextDirection.LeftRight_TopBottom)] + [InlineData (1, 14, TextDirection.TopBottom_LeftRight)] + public void CalcRect_With_Combining_Runes (int width, int height, TextDirection textDirection) + { + var text = "Les Mise\u0328\u0301rables"; + Assert.Equal (new Rect (0, 0, width, height), TextFormatter.CalcRect (0, 0, text, textDirection)); + } + + [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 (); + 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 text = "Les Mise\u0328\u0301rables"; - var tf = new TextFormatter (); - tf.Direction = textDirection; - tf.Text = text; + var tf = new TextFormatter (); + tf.Direction = textDirection; + tf.Text = text; - Assert.True (tf.WordWrap); - if (textDirection == TextDirection.LeftRight_TopBottom) { - Assert.Equal (new Size (width, height), tf.Size); - } else { - Assert.Equal (new Size (1, text.GetColumns ()), tf.Size); - tf.Size = new Size (width, height); - } - tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, 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_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; - - Assert.True (tf.WordWrap); - Assert.False (tf.PreserveTrailingSpaces); + Assert.True (tf.WordWrap); + if (textDirection == TextDirection.LeftRight_TopBottom) { Assert.Equal (new Size (width, height), tf.Size); - tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); - TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver); - - driver.End (); + } else { + Assert.Equal (new Size (1, text.GetColumns ()), tf.Size); + tf.Size = new Size (width, height); } + tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); + TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver); - [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 (); + driver.End (); + } - var text = "This is a \tTab"; - var tf = new TextFormatter (); - tf.Direction = textDirection; - tf.TabWidth = tabWidth; - tf.PreserveTrailingSpaces = true; - tf.Text = text; + [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 (); - Assert.True (tf.WordWrap); - Assert.Equal (new Size (width, height), tf.Size); - tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); - TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver); + var text = "This is a \tTab"; + var tf = new TextFormatter (); + tf.Direction = textDirection; + tf.TabWidth = tabWidth; + tf.Text = text; - driver.End (); - } + Assert.True (tf.WordWrap); + Assert.False (tf.PreserveTrailingSpaces); + Assert.Equal (new Size (width, height), tf.Size); + tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); + TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver); - [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 (); + driver.End (); + } - var text = "This is a \tTab"; - var tf = new TextFormatter (); - tf.Direction = textDirection; - tf.TabWidth = tabWidth; - tf.WordWrap = true; - tf.Text = text; + [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 (); - Assert.False (tf.PreserveTrailingSpaces); - Assert.Equal (new Size (width, height), tf.Size); - tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); - TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver); + var text = "This is a \tTab"; + var tf = new TextFormatter (); + tf.Direction = textDirection; + tf.TabWidth = tabWidth; + tf.PreserveTrailingSpaces = true; + tf.Text = text; - driver.End (); - } + Assert.True (tf.WordWrap); + Assert.Equal (new Size (width, height), tf.Size); + tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, 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; + + Assert.False (tf.PreserveTrailingSpaces); + Assert.Equal (new Size (width, height), tf.Size); + tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver); + TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver); + + driver.End (); } } \ No newline at end of file diff --git a/UnitTests/View/Layout/DimTests.cs b/UnitTests/View/Layout/DimTests.cs index 727c4e7ff..e172b25b1 100644 --- a/UnitTests/View/Layout/DimTests.cs +++ b/UnitTests/View/Layout/DimTests.cs @@ -559,8 +559,6 @@ public class DimTests { t.BeginInit (); t.EndInit (); - // BUGBUG: v2 - f references t here; t is f's super-superview. This is supported! - // BUGBUG: v2 - f references v2 here; v2 is f's subview. This is not supported! f.Width = Dim.Width (t) - Dim.Width (v2); // 80 - 74 = 6 f.Height = Dim.Height (t) - Dim.Height (v2); // 25 - 19 = 6 @@ -569,13 +567,12 @@ public class DimTests { Assert.Equal (25, t.Frame.Height); Assert.Equal (78, w.Frame.Width); Assert.Equal (23, w.Frame.Height); - // BUGBUG: v2 - this no longer works - see above - //Assert.Equal (6, f.Frame.Width); - //Assert.Equal (6, f.Frame.Height); - //Assert.Equal (76, v1.Frame.Width); - //Assert.Equal (21, v1.Frame.Height); - //Assert.Equal (74, v2.Frame.Width); - //Assert.Equal (19, v2.Frame.Height); + Assert.Equal (6, f.Frame.Width); + Assert.Equal (6, f.Frame.Height); + Assert.Equal (76, v1.Frame.Width); + Assert.Equal (21, v1.Frame.Height); + Assert.Equal (74, v2.Frame.Width); + Assert.Equal (19, v2.Frame.Height); t.Dispose (); } @@ -632,7 +629,6 @@ public class DimTests { { var t = new View { Width = 80, Height = 50 }; - // BUGBUG: v2 - super should not reference it's superview (t) var super = new View { Width = Dim.Width (t) - 2, Height = Dim.Height (t) - 2 @@ -720,19 +716,17 @@ public class DimTests { var count = 20; var listLabels = new List