// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Reactive.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Markup.Xaml.Data; using Avalonia.Platform; using Avalonia.UnitTests; using Moq; using Xunit; namespace Avalonia.Controls.UnitTests { public class TextBoxTests { [Fact] public void DefaultBindingMode_Should_Be_TwoWay() { Assert.Equal( BindingMode.TwoWay, TextBox.TextProperty.GetMetadata(typeof(TextBox)).DefaultBindingMode); } [Fact] public void CaretIndex_Can_Moved_To_Position_After_The_End_Of_Text_With_Arrow_Key() { using (UnitTestApplication.Start(Services)) { var target = new TextBox { Template = CreateTemplate(), Text = "1234" }; target.CaretIndex = 3; RaiseKeyEvent(target, Key.Right, 0); Assert.Equal(4, target.CaretIndex); } } [Fact] public void Press_Ctrl_A_Select_All_Text() { using (UnitTestApplication.Start(Services)) { var target = new TextBox { Template = CreateTemplate(), Text = "1234" }; RaiseKeyEvent(target, Key.A, InputModifiers.Control); Assert.Equal(0, target.SelectionStart); Assert.Equal(4, target.SelectionEnd); } } [Fact] public void Press_Ctrl_A_Select_All_Null_Text() { using (UnitTestApplication.Start(Services)) { var target = new TextBox { Template = CreateTemplate() }; RaiseKeyEvent(target, Key.A, InputModifiers.Control); Assert.Equal(0, target.SelectionStart); Assert.Equal(0, target.SelectionEnd); } } [Fact] public void Press_Ctrl_Z_Will_Not_Modify_Text() { using (UnitTestApplication.Start(Services)) { var target = new TextBox { Template = CreateTemplate(), Text = "1234" }; RaiseKeyEvent(target, Key.Z, InputModifiers.Control); Assert.Equal("1234", target.Text); } } [Fact] public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int() { using (UnitTestApplication.Start(Services)) { var source = new Class1(); var target = new TextBox { DataContext = source, Template = CreateTemplate(), }; target.ApplyTemplate(); target.Bind(TextBox.TextProperty, new Binding(nameof(Class1.Foo), BindingMode.TwoWay)); Assert.Equal("0", target.Text); target.CaretIndex = 1; target.RaiseEvent(new TextInputEventArgs { RoutedEvent = InputElement.TextInputEvent, Text = "2", }); Assert.Equal("02", target.Text); } } [Fact] public void Control_Backspace_Should_Remove_The_Word_Before_The_Caret_If_There_Is_No_Selection() { using (UnitTestApplication.Start(Services)) { TextBox textBox = new TextBox { Text = "First Second Third Fourth", CaretIndex = 5 }; // (First| Second Third Fourth) RaiseKeyEvent(textBox, Key.Back, InputModifiers.Control); Assert.Equal(" Second Third Fourth", textBox.Text); // ( Second |Third Fourth) textBox.CaretIndex = 8; RaiseKeyEvent(textBox, Key.Back, InputModifiers.Control); Assert.Equal(" Third Fourth", textBox.Text); // ( Thi|rd Fourth) textBox.CaretIndex = 4; RaiseKeyEvent(textBox, Key.Back, InputModifiers.Control); Assert.Equal(" rd Fourth", textBox.Text); // ( rd F[ou]rth) textBox.SelectionStart = 5; textBox.SelectionEnd = 7; RaiseKeyEvent(textBox, Key.Back, InputModifiers.Control); Assert.Equal(" rd Frth", textBox.Text); // ( |rd Frth) textBox.CaretIndex = 1; RaiseKeyEvent(textBox, Key.Back, InputModifiers.Control); Assert.Equal("rd Frth", textBox.Text); } } [Fact] public void Control_Delete_Should_Remove_The_Word_After_The_Caret_If_There_Is_No_Selection() { using (UnitTestApplication.Start(Services)) { TextBox textBox = new TextBox { Text = "First Second Third Fourth", CaretIndex = 19 }; // (First Second Third |Fourth) RaiseKeyEvent(textBox, Key.Delete, InputModifiers.Control); Assert.Equal("First Second Third ", textBox.Text); // (First Second |Third ) textBox.CaretIndex = 13; RaiseKeyEvent(textBox, Key.Delete, InputModifiers.Control); Assert.Equal("First Second ", textBox.Text); // (First Sec|ond ) textBox.CaretIndex = 9; RaiseKeyEvent(textBox, Key.Delete, InputModifiers.Control); Assert.Equal("First Sec", textBox.Text); // (Fi[rs]t Sec ) textBox.SelectionStart = 2; textBox.SelectionEnd = 4; RaiseKeyEvent(textBox, Key.Delete, InputModifiers.Control); Assert.Equal("Fit Sec", textBox.Text); // (Fit Sec| ) textBox.Text += " "; textBox.CaretIndex = 7; RaiseKeyEvent(textBox, Key.Delete, InputModifiers.Control); Assert.Equal("Fit Sec", textBox.Text); } } [Fact] public void Setting_Text_Updates_CaretPosition() { using (UnitTestApplication.Start(Services)) { var target = new TextBox { Text = "Initial Text", CaretIndex = 11 }; var invoked = false; target.GetObservable(TextBox.TextProperty).Skip(1).Subscribe(_ => { // Caret index should be set before Text changed notification, as we don't want // to notify with an invalid CaretIndex. Assert.Equal(7, target.CaretIndex); invoked = true; }); target.Text = "Changed"; Assert.True(invoked); } } private static TestServices Services => TestServices.MockThreadingInterface.With( standardCursorFactory: Mock.Of()); private IControlTemplate CreateTemplate() { return new FuncControlTemplate(control => new TextPresenter { Name = "PART_TextPresenter", [!!TextPresenter.TextProperty] = new Binding { Path = "Text", Mode = BindingMode.TwoWay, Priority = BindingPriority.TemplatedParent, RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), }, }); } private void RaiseKeyEvent(TextBox textBox, Key key, InputModifiers inputModifiers) { textBox.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyDownEvent, Modifiers = inputModifiers, Key = key }); } private class Class1 : NotifyingBase { private int _foo; public int Foo { get { return _foo; } set { _foo = value; RaisePropertyChanged(); } } } } }