using Xunit.Abstractions; namespace Terminal.Gui.TextTests; public class AppendAutocompleteTests (ITestOutputHelper output) { [Fact] [AutoInitShutdown] public void TestAutoAppend_AfterCloseKey_NoAutocomplete () { TextField tf = GetTextFieldsInViewSuggesting ("fish"); // f is typed and suggestion is "fish" Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); // When cancelling autocomplete Application.Driver?.SendKeys ('e', ConsoleKey.Escape, false, false, false); // Suggestion should disappear tf.Draw (); TestHelpers.AssertDriverContentsAre ("f", output); Assert.Equal ("f", tf.Text); // Still has focus though Assert.Same (tf, Application.Top.Focused); // But can tab away Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); Assert.NotSame (tf, Application.Top.Focused); Application.Top.Dispose (); } [Fact] [AutoInitShutdown] public void TestAutoAppend_AfterCloseKey_ReappearsOnLetter () { TextField tf = GetTextFieldsInViewSuggesting ("fish"); // f is typed and suggestion is "fish" Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); // When cancelling autocomplete Application.Driver?.SendKeys ('\0', ConsoleKey.Escape, false, false, false); // Suggestion should disappear tf.Draw (); TestHelpers.AssertDriverContentsAre ("f", output); Assert.Equal ("f", tf.Text); // Should reappear when you press next letter Application.Driver?.SendKeys ('i', ConsoleKey.I, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("fi", tf.Text); Application.Top.Dispose (); } [Theory] [AutoInitShutdown] [InlineData (ConsoleKey.UpArrow)] [InlineData (ConsoleKey.DownArrow)] public void TestAutoAppend_CycleSelections (ConsoleKey cycleKey) { TextField tf = GetTextFieldsInViewSuggesting ("fish", "friend"); // f is typed and suggestion is "fish" Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); // When cycling autocomplete Application.Driver?.SendKeys (' ', cycleKey, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("friend", output); Assert.Equal ("f", tf.Text); // Should be able to cycle in circles endlessly Application.Driver?.SendKeys (' ', cycleKey, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); Application.Top.Dispose (); } [Fact] [AutoInitShutdown] public void TestAutoAppend_NoRender_WhenCursorNotAtEnd () { TextField tf = GetTextFieldsInViewSuggesting ("fish"); // f is typed and suggestion is "fish" Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); // add a space then go back 1 Application.Driver?.SendKeys (' ', ConsoleKey.Spacebar, false, false, false); Application.Driver?.SendKeys ('<', ConsoleKey.LeftArrow, false, false, false); tf.Draw (); TestHelpers.AssertDriverContentsAre ("f", output); Assert.Equal ("f ", tf.Text); Application.Top.Dispose (); } [Fact] [AutoInitShutdown] public void TestAutoAppend_NoRender_WhenNoMatch () { TextField tf = GetTextFieldsInViewSuggesting ("fish"); // f is typed and suggestion is "fish" Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); // x is typed and suggestion should disappear Application.Driver?.SendKeys ('x', ConsoleKey.X, false, false, false); tf.Draw (); TestHelpers.AssertDriverContentsAre ("fx", output); Assert.Equal ("fx", tf.Text); Application.Top.Dispose (); } [Fact] [AutoInitShutdown] public void TestAutoAppend_ShowThenAccept_CasesDiffer () { TextField tf = GetTextFieldsInView (); tf.Autocomplete = new AppendAutocomplete (tf); var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator; generator.AllSuggestions = new List { "FISH" }; tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("", output); tf.NewKeyDownEvent (Key.M); tf.NewKeyDownEvent (Key.Y); tf.NewKeyDownEvent (Key.Space); tf.NewKeyDownEvent (Key.F); Assert.Equal ("my f", tf.Text); // Even though there is no match on case we should still get the suggestion tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("my fISH", output); Assert.Equal ("my f", tf.Text); // When tab completing the case of the whole suggestion should be applied Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); tf.Draw (); TestHelpers.AssertDriverContentsAre ("my FISH", output); Assert.Equal ("my FISH", tf.Text); Application.Top.Dispose (); } [Fact] [AutoInitShutdown] public void TestAutoAppend_ShowThenAccept_MatchCase () { TextField tf = GetTextFieldsInView (); tf.Autocomplete = new AppendAutocomplete (tf); var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator; generator.AllSuggestions = new List { "fish" }; tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("", output); tf.NewKeyDownEvent (new Key ('f')); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("f", tf.Text); Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); tf.Draw (); TestHelpers.AssertDriverContentsAre ("fish", output); Assert.Equal ("fish", tf.Text); // Tab should autcomplete but not move focus Assert.Same (tf, Application.Top.Focused); // Second tab should move focus (nothing to autocomplete) Application.Driver?.SendKeys ('\t', ConsoleKey.Tab, false, false, false); Assert.NotSame (tf, Application.Top.Focused); Application.Top.Dispose (); } [Theory] [AutoInitShutdown] [InlineData ("ffffffffffffffffffffffffff", "ffffffffff")] [InlineData ("f234567890", "f234567890")] [InlineData ("fisérables", "fisérables")] public void TestAutoAppendRendering_ShouldNotOverspill (string overspillUsing, string expectRender) { TextField tf = GetTextFieldsInViewSuggesting (overspillUsing); // f is typed we should only see 'f' up to size of View (10) Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre (expectRender, output); Assert.Equal ("f", tf.Text); Application.Top.Dispose (); } private TextField GetTextFieldsInView () { var tf = new TextField { Width = 10 }; var tf2 = new TextField { Y = 1, Width = 10 }; Toplevel top = new (); top.Add (tf); top.Add (tf2); Application.Begin (top); Assert.Same (tf, top.Focused); return tf; } private TextField GetTextFieldsInViewSuggesting (params string [] suggestions) { TextField tf = GetTextFieldsInView (); tf.Autocomplete = new AppendAutocomplete (tf); var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator; generator.AllSuggestions = suggestions.ToList (); tf.Draw (); tf.PositionCursor (); TestHelpers.AssertDriverContentsAre ("", output); return tf; } }