committed by
GitHub
6 changed files with 235 additions and 5 deletions
@ -0,0 +1,174 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Data; |
|||
using Avalonia.Input; |
|||
using Avalonia.Threading; |
|||
using Avalonia.UnitTests; |
|||
using Xunit; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Markup.UnitTests.Data; |
|||
|
|||
public class BindingTests_Delay : IDisposable |
|||
{ |
|||
private const int DelayMilliseconds = 10; |
|||
private const string InitialFooValue = "foo"; |
|||
|
|||
private readonly ManualTimerDispatcher _dispatcher; |
|||
private readonly IDisposable _app; |
|||
private readonly BindingTests.Source _source; |
|||
private readonly TextBox _target; |
|||
private readonly Binding _binding; |
|||
private readonly BindingExpressionBase _bindingExpr; |
|||
|
|||
public BindingTests_Delay() |
|||
{ |
|||
_dispatcher = new ManualTimerDispatcher(); |
|||
_app = UnitTestApplication.Start(new(dispatcherImpl: _dispatcher, focusManager: new FocusManager(), keyboardDevice: () => new KeyboardDevice())); |
|||
|
|||
_source = new BindingTests.Source { Foo = InitialFooValue }; |
|||
_target = new TextBox { DataContext = _source }; |
|||
_binding = new Binding(nameof(_source.Foo), BindingMode.TwoWay) { Delay = DelayMilliseconds }; |
|||
|
|||
_bindingExpr = _target.Bind(TextBox.TextProperty, _binding); |
|||
|
|||
Assert.Equal(_source.Foo, _target.Text); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_app.Dispose(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Delayed_Binding_Should_Set_Value_Only_After_Delay_Elapsed() |
|||
{ |
|||
_target.Text = "bar"; |
|||
Assert.Equal(InitialFooValue, _source.Foo); |
|||
|
|||
SetTimeAndExecuteTimers(DelayMilliseconds / 2); |
|||
Assert.Equal(InitialFooValue, _source.Foo); |
|||
|
|||
SetTimeAndExecuteTimers(DelayMilliseconds + 1); |
|||
|
|||
Assert.Equal("bar", _source.Foo); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Delayed_Binding_Should_Not_Set_Value_After_Being_Disposed() |
|||
{ |
|||
_target.Text = "bar"; |
|||
Assert.Equal(InitialFooValue, _source.Foo); |
|||
|
|||
_bindingExpr.Dispose(); |
|||
|
|||
SetTimeAndExecuteTimers(DelayMilliseconds + 1); |
|||
|
|||
Assert.Equal(InitialFooValue, _source.Foo); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Delayed_Binding_Should_Restart_If_Value_Changes_During_Delay() |
|||
{ |
|||
_target.Text = "bar"; |
|||
Assert.Equal(InitialFooValue, _source.Foo); |
|||
|
|||
SetTimeAndExecuteTimers(DelayMilliseconds / 2); |
|||
|
|||
_target.Text = "baz"; |
|||
|
|||
SetTimeAndExecuteTimers(DelayMilliseconds + 1); // we set a new value half-way through the delay, so the delay is still in effect at this timestamp
|
|||
|
|||
Assert.Equal(InitialFooValue, _source.Foo); |
|||
|
|||
SetTimeAndExecuteTimers(DelayMilliseconds * 2); |
|||
|
|||
Assert.Equal("baz", _source.Foo); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Delayed_Binding_Should_Not_Execute_If_Value_Returns_To_Original() |
|||
{ |
|||
_target.Text = "bar"; |
|||
Assert.Equal(InitialFooValue, _source.Foo); |
|||
|
|||
SetTimeAndExecuteTimers(DelayMilliseconds / 2); |
|||
|
|||
_target.Text = InitialFooValue; |
|||
|
|||
SetTimeAndExecuteTimers(DelayMilliseconds * 2); |
|||
|
|||
Assert.Equal(InitialFooValue, _source.Foo); |
|||
Assert.Equal(1, _source.FooSetCount); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Delayed_Binding_UpdateSource_Call_Should_Update_Source_Immediately() |
|||
{ |
|||
_target.Text = "bar"; |
|||
_bindingExpr.UpdateSource(); |
|||
|
|||
Assert.Equal("bar", _source.Foo); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Delayed_Binding_UpdateTrigger_LostFocus_Should_Update_Source_Immediately() |
|||
{ |
|||
var secondBox = new TextBox(); |
|||
|
|||
new TestRoot() { Child = new Panel() { Children = { _target, secondBox } } }; |
|||
|
|||
_target.Bind(TextBox.TextProperty, new Binding(nameof(_source.Foo), BindingMode.TwoWay) { Delay = DelayMilliseconds, UpdateSourceTrigger = UpdateSourceTrigger.LostFocus }); |
|||
|
|||
Assert.True(_target.Focus()); |
|||
_target.Text = "bar"; |
|||
|
|||
Assert.Equal(InitialFooValue, _source.Foo); |
|||
|
|||
Assert.True(secondBox.Focus()); |
|||
Assert.Equal("bar", _source.Foo); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Delayed_Binding_OneWayToSource_DataContext_Change_Should_Update_Source_Immediately() |
|||
{ |
|||
_target.Bind(TextBlock.TextProperty, new Binding(nameof(_source.Foo), BindingMode.OneWayToSource) { Delay = DelayMilliseconds }); |
|||
|
|||
_target.Text = "bar"; |
|||
|
|||
var newSource = new BindingTests.Source(); |
|||
|
|||
_target.DataContext = newSource; |
|||
|
|||
Assert.Equal("bar", newSource.Foo); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Delayed_Binding_Should_Update_Target_Immediately() |
|||
{ |
|||
_source.Foo = "bar"; |
|||
Assert.Equal("bar", _target.Text); |
|||
} |
|||
|
|||
private void SetTimeAndExecuteTimers(long time) |
|||
{ |
|||
_dispatcher.Now = time; |
|||
_dispatcher.RaiseTimerEvent(); |
|||
} |
|||
|
|||
private class ManualTimerDispatcher : IDispatcherImpl |
|||
{ |
|||
public bool CurrentThreadIsLoopThread => true; |
|||
public long Now { get; set; } |
|||
|
|||
public event Action? Signaled; |
|||
public event Action? Timer; |
|||
|
|||
public void Signal() { Signaled?.Invoke(); } |
|||
|
|||
public void UpdateTimer(long? dueTimeInMs) { } |
|||
|
|||
public void RaiseTimerEvent() => Timer?.Invoke(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue