Browse Source

Fixed tests, reintroduced Post with SendOrPostCallback, removed JobRunner

pull/10691/head
Nikita Tsukanov 4 years ago
parent
commit
968e61f649
  1. 12
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  2. 16
      src/Avalonia.Base/Threading/Dispatcher.Queue.cs
  3. 4
      src/Avalonia.Base/Threading/Dispatcher.Timers.cs
  4. 6
      src/Avalonia.Base/Threading/Dispatcher.cs
  5. 39
      src/Avalonia.Base/Threading/DispatcherOperation.cs
  6. 300
      src/Avalonia.Base/Threading/JobRunner.cs
  7. 17
      src/Avalonia.Base/Utilities/DispatcherTimerHelper.cs
  8. 127
      tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs
  9. 25
      tests/Avalonia.Controls.UnitTests/ToolTipTests.cs

12
src/Avalonia.Base/Threading/Dispatcher.Invoke.cs

@ -538,4 +538,16 @@ public partial class Dispatcher
_ = action ?? throw new ArgumentNullException(nameof(action));
InvokeAsyncImpl(new DispatcherOperation(this, priority, action, true), CancellationToken.None);
}
/// <summary>
/// Posts an action that will be invoked on the dispatcher thread.
/// </summary>
/// <param name="action">The method.</param>
/// <param name="arg">The argument of method to call.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
public void Post(SendOrPostCallback action, object? arg, DispatcherPriority priority = default)
{
_ = action ?? throw new ArgumentNullException(nameof(action));
InvokeAsyncImpl(new SendOrPostCallbackDispatcherOperation(this, priority, action, arg, true), CancellationToken.None);
}
}

16
src/Avalonia.Base/Threading/Dispatcher.Queue.cs

@ -47,6 +47,20 @@ public partial class Dispatcher
}
}
class DummyShuttingDownUnitTestDispatcherImpl : IDispatcherImpl
{
public bool CurrentThreadIsLoopThread => true;
public void Signal()
{
}
public event Action? Signaled;
public event Action? Timer;
public void UpdateTimer(int? dueTimeInTicks)
{
}
}
internal static void ResetForUnitTests()
{
if (s_uiThread == null)
@ -54,6 +68,8 @@ public partial class Dispatcher
var st = Stopwatch.StartNew();
while (true)
{
s_uiThread._pendingInputImpl = s_uiThread._controlledImpl = null;
s_uiThread._impl = new DummyShuttingDownUnitTestDispatcherImpl();
if (st.Elapsed.TotalSeconds > 5)
throw new InvalidProgramException("You've caused dispatcher loop");

4
src/Avalonia.Base/Threading/Dispatcher.Timers.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Threading;
@ -168,4 +169,7 @@ public partial class Dispatcher
UpdateOSTimer();
}
}
internal static List<DispatcherTimer> SnapshotTimersForUnitTests() =>
s_uiThread!._timers.Where(t => t != s_uiThread._backgroundTimer).ToList();
}

6
src/Avalonia.Base/Threading/Dispatcher.cs

@ -16,13 +16,13 @@ namespace Avalonia.Threading;
/// </remarks>
public partial class Dispatcher : IDispatcher
{
private readonly IDispatcherImpl _impl;
private IDispatcherImpl _impl;
internal IDispatcherClock Clock { get; }
internal object InstanceLock { get; } = new();
private bool _hasShutdownFinished;
private readonly IControlledDispatcherImpl? _controlledImpl;
private IControlledDispatcherImpl? _controlledImpl;
private static Dispatcher? s_uiThread;
private readonly IDispatcherImplWithPendingInput? _pendingInputImpl;
private IDispatcherImplWithPendingInput? _pendingInputImpl;
internal Dispatcher(IDispatcherImpl impl, IDispatcherClock clock)
{

39
src/Avalonia.Base/Threading/DispatcherOperation.cs

@ -261,6 +261,45 @@ public class DispatcherOperation<T> : DispatcherOperation
}
}
internal class SendOrPostCallbackDispatcherOperation : DispatcherOperation
{
private readonly object? _arg;
internal SendOrPostCallbackDispatcherOperation(Dispatcher dispatcher, DispatcherPriority priority,
SendOrPostCallback callback, object? arg, bool throwOnUiThread)
: base(dispatcher, priority, throwOnUiThread)
{
Callback = callback;
_arg = arg;
}
protected override void InvokeCore()
{
try
{
((SendOrPostCallback)Callback!)(_arg);
lock (Dispatcher.InstanceLock)
{
Status = DispatcherOperationStatus.Completed;
if (TaskSource is TaskCompletionSource<object?> tcs)
tcs.SetResult(null);
}
}
catch (Exception e)
{
lock (Dispatcher.InstanceLock)
{
Status = DispatcherOperationStatus.Completed;
if (TaskSource is TaskCompletionSource<object?> tcs)
tcs.SetException(e);
}
if (ThrowOnUiThread)
throw;
}
}
}
public enum DispatcherOperationStatus
{
Pending = 0,

300
src/Avalonia.Base/Threading/JobRunner.cs

@ -1,300 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Threading
{
/// <summary>
/// A main loop in a <see cref="Dispatcher"/>.
/// </summary>
internal class JobRunner
{
private IPlatformThreadingInterface? _platform;
private readonly Queue<IJob>[] _queues = Enumerable.Range(0, (int)DispatcherPriority.MaxValue + 1)
.Select(_ => new Queue<IJob>()).ToArray();
public JobRunner(IPlatformThreadingInterface? platform)
{
_platform = platform;
}
/// <summary>
/// Runs continuations pushed on the loop.
/// </summary>
/// <param name="priority">Priority to execute jobs for. Pass null if platform doesn't have internal priority system</param>
public void RunJobs(DispatcherPriority? priority)
{
var minimumPriority = priority ?? DispatcherPriority.MinimumActiveValue;
while (true)
{
var job = GetNextJob(minimumPriority);
if (job == null)
return;
job.Run();
}
}
/// <summary>
/// Invokes a method on the main loop.
/// </summary>
/// <param name="action">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
/// <returns>A task that can be used to track the method's execution.</returns>
public Task InvokeAsync(Action action, DispatcherPriority priority)
{
var job = new Job(action, priority, false);
AddJob(job);
return job.Task!;
}
/// <summary>
/// Invokes a method on the main loop.
/// </summary>
/// <param name="function">The method.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
/// <returns>A task that can be used to track the method's execution.</returns>
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority)
{
var job = new JobWithResult<TResult>(function, priority);
AddJob(job);
return job.Task;
}
/// <summary>
/// Post action that will be invoked on main thread
/// </summary>
/// <param name="action">The method.</param>
///
/// <param name="priority">The priority with which to invoke the method.</param>
internal void Post(Action action, DispatcherPriority priority)
{
AddJob(new Job(action, priority, true));
}
/// <summary>
/// Post action that will be invoked on main thread
/// </summary>
/// <param name="action">The method to call.</param>
/// <param name="parameter">The parameter of method to call.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
internal void Post(SendOrPostCallback action, object? parameter, DispatcherPriority priority)
{
AddJob(new JobWithArg(action, parameter, priority, true));
}
/// <summary>
/// Allows unit tests to change the platform threading interface.
/// </summary>
internal void UpdateServices()
{
_platform = AvaloniaLocator.Current.GetService<IPlatformThreadingInterface>();
}
private void AddJob(IJob job)
{
bool needWake;
var queue = _queues[(int)job.Priority];
lock (queue)
{
needWake = queue.Count == 0;
queue.Enqueue(job);
}
if (needWake)
_platform?.Signal(job.Priority);
}
private IJob? GetNextJob(DispatcherPriority minimumPriority)
{
for (int c = (int)DispatcherPriority.MaxValue; c >= (int)minimumPriority; c--)
{
var q = _queues[c];
lock (q)
{
if (q.Count > 0)
return q.Dequeue();
}
}
return null;
}
public bool HasJobsWithPriority(DispatcherPriority minimumPriority)
{
for (int c = (int)minimumPriority; c < (int)DispatcherPriority.MaxValue; c++)
{
var q = _queues[c];
lock (q)
{
if (q.Count > 0)
return true;
}
}
return false;
}
private interface IJob
{
/// <summary>
/// Gets the job priority.
/// </summary>
DispatcherPriority Priority { get; }
/// <summary>
/// Runs the job.
/// </summary>
void Run();
}
/// <summary>
/// A job to run.
/// </summary>
private sealed class Job : IJob
{
/// <summary>
/// The method to call.
/// </summary>
private readonly Action _action;
/// <summary>
/// The task completion source.
/// </summary>
private readonly TaskCompletionSource<object?>? _taskCompletionSource;
/// <summary>
/// Initializes a new instance of the <see cref="Job"/> class.
/// </summary>
/// <param name="action">The method to call.</param>
/// <param name="priority">The job priority.</param>
/// <param name="throwOnUiThread">Do not wrap exception in TaskCompletionSource</param>
public Job(Action action, DispatcherPriority priority, bool throwOnUiThread)
{
_action = action;
Priority = priority;
_taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<object?>();
}
/// <inheritdoc/>
public DispatcherPriority Priority { get; }
/// <summary>
/// The task.
/// </summary>
public Task? Task => _taskCompletionSource?.Task;
/// <inheritdoc/>
void IJob.Run()
{
if (_taskCompletionSource == null)
{
_action();
return;
}
try
{
_action();
_taskCompletionSource.SetResult(null);
}
catch (Exception e)
{
_taskCompletionSource.SetException(e);
}
}
}
/// <summary>
/// A typed job to run.
/// </summary>
private sealed class JobWithArg : IJob
{
private readonly SendOrPostCallback _action;
private readonly object? _parameter;
private readonly TaskCompletionSource<bool>? _taskCompletionSource;
/// <summary>
/// Initializes a new instance of the <see cref="Job"/> class.
/// </summary>
/// <param name="action">The method to call.</param>
/// <param name="parameter">The parameter of method to call.</param>
/// <param name="priority">The job priority.</param>
/// <param name="throwOnUiThread">Do not wrap exception in TaskCompletionSource</param>
public JobWithArg(SendOrPostCallback action, object? parameter, DispatcherPriority priority, bool throwOnUiThread)
{
_action = action;
_parameter = parameter;
Priority = priority;
_taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<bool>();
}
/// <inheritdoc/>
public DispatcherPriority Priority { get; }
/// <inheritdoc/>
void IJob.Run()
{
if (_taskCompletionSource == null)
{
_action(_parameter);
return;
}
try
{
_action(_parameter);
_taskCompletionSource.SetResult(default);
}
catch (Exception e)
{
_taskCompletionSource.SetException(e);
}
}
}
/// <summary>
/// A job to run thath return value.
/// </summary>
/// <typeparam name="TResult">Type of job result</typeparam>
private sealed class JobWithResult<TResult> : IJob
{
private readonly Func<TResult> _function;
private readonly TaskCompletionSource<TResult> _taskCompletionSource;
/// <summary>
/// Initializes a new instance of the <see cref="Job"/> class.
/// </summary>
/// <param name="function">The method to call.</param>
/// <param name="priority">The job priority.</param>
public JobWithResult(Func<TResult> function, DispatcherPriority priority)
{
_function = function;
Priority = priority;
_taskCompletionSource = new TaskCompletionSource<TResult>();
}
/// <inheritdoc/>
public DispatcherPriority Priority { get; }
/// <summary>
/// The task.
/// </summary>
public Task<TResult> Task => _taskCompletionSource.Task;
/// <inheritdoc/>
void IJob.Run()
{
try
{
var result = _function();
_taskCompletionSource.SetResult(result);
}
catch (Exception e)
{
_taskCompletionSource.SetException(e);
}
}
}
}
}

17
src/Avalonia.Base/Utilities/DispatcherTimerHelper.cs

@ -0,0 +1,17 @@
using Avalonia.Threading;
namespace Avalonia.Utilities;
public class DispatcherTimerHelper
{
}
public static class DispatcherTimerUtils
{
public static void ForceFire(this DispatcherTimer timer)
{
timer.Promote();
timer.Dispatcher.RunJobs();
}
}

127
tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
@ -7,14 +8,16 @@ using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.Utilities;
using Moq;
using Xunit;
// ReSharper disable RedundantArgumentDefaultValue
namespace Avalonia.Base.UnitTests.Input
{
public class GesturesTests
{
private MouseTestHelper _mouse = new MouseTestHelper();
private readonly MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Tapped_Should_Follow_Pointer_Pressed_Released()
@ -60,7 +63,7 @@ namespace Avalonia.Base.UnitTests.Input
};
var raised = false;
decorator.AddHandler(Gestures.TappedEvent, (s, e) => raised = true);
decorator.AddHandler(Gestures.TappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Middle);
@ -77,7 +80,7 @@ namespace Avalonia.Base.UnitTests.Input
};
var raised = false;
decorator.AddHandler(Gestures.TappedEvent, (s, e) => raised = true);
decorator.AddHandler(Gestures.TappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Right);
@ -94,7 +97,7 @@ namespace Avalonia.Base.UnitTests.Input
};
var raised = false;
decorator.AddHandler(Gestures.RightTappedEvent, (s, e) => raised = true);
decorator.AddHandler(Gestures.RightTappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Right);
@ -147,7 +150,7 @@ namespace Avalonia.Base.UnitTests.Input
};
var raised = false;
decorator.AddHandler(Gestures.DoubleTappedEvent, (s, e) => raised = true);
decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Middle);
_mouse.Down(border, MouseButton.Middle, clickCount: 2);
@ -165,7 +168,7 @@ namespace Avalonia.Base.UnitTests.Input
};
var raised = false;
decorator.AddHandler(Gestures.DoubleTappedEvent, (s, e) => raised = true);
decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Right);
_mouse.Down(border, MouseButton.Right, clickCount: 2);
@ -182,10 +185,8 @@ namespace Avalonia.Base.UnitTests.Input
iSettingsMock.Setup(x => x.GetTapSize(It.IsAny<PointerType>())).Returns(new Size(16, 16));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(iSettingsMock.Object);
var scheduledTimers = new List<(TimeSpan time, Action action)>();
using var app = UnitTestApplication.Start(new TestServices(
threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
using var app = UnitTestApplication.Start();
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
@ -195,15 +196,15 @@ namespace Avalonia.Base.UnitTests.Input
};
HoldingState holding = HoldingState.Cancelled;
decorator.AddHandler(Gestures.HoldingEvent, (s, e) => holding = e.HoldingState);
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => holding = e.HoldingState);
_mouse.Down(border);
Assert.False(holding != HoldingState.Cancelled);
// Verify timer duration, but execute it immediately.
var timer = Assert.Single(scheduledTimers);
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
timer.action();
var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests());
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.Interval);
timer.ForceFire();
Assert.True(holding == HoldingState.Started);
@ -220,10 +221,8 @@ namespace Avalonia.Base.UnitTests.Input
iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(iSettingsMock.Object);
var scheduledTimers = new List<(TimeSpan time, Action action)>();
using var app = UnitTestApplication.Start(new TestServices(
threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
using var app = UnitTestApplication.Start();
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
@ -233,7 +232,7 @@ namespace Avalonia.Base.UnitTests.Input
};
var raised = false;
decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Started);
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Started);
_mouse.Down(border);
Assert.False(raised);
@ -242,9 +241,9 @@ namespace Avalonia.Base.UnitTests.Input
Assert.False(raised);
// Verify timer duration, but execute it immediately.
var timer = Assert.Single(scheduledTimers);
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
timer.action();
var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests());
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.Interval);
timer.ForceFire();
Assert.False(raised);
}
@ -257,10 +256,8 @@ namespace Avalonia.Base.UnitTests.Input
iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(iSettingsMock.Object);
var scheduledTimers = new List<(TimeSpan time, Action action)>();
using var app = UnitTestApplication.Start(new TestServices(
threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
using var app = UnitTestApplication.Start();
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
@ -270,7 +267,7 @@ namespace Avalonia.Base.UnitTests.Input
};
var raised = false;
decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Completed);
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Completed);
_mouse.Down(border);
Assert.False(raised);
@ -279,9 +276,9 @@ namespace Avalonia.Base.UnitTests.Input
Assert.False(raised);
// Verify timer duration, but execute it immediately.
var timer = Assert.Single(scheduledTimers);
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
timer.action();
var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests());
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.Interval);
timer.ForceFire();
Assert.False(raised);
}
@ -294,10 +291,8 @@ namespace Avalonia.Base.UnitTests.Input
iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(iSettingsMock.Object);
var scheduledTimers = new List<(TimeSpan time, Action action)>();
using var app = UnitTestApplication.Start(new TestServices(
threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
using var app = UnitTestApplication.Start();
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
@ -307,14 +302,14 @@ namespace Avalonia.Base.UnitTests.Input
};
var cancelled = false;
decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled);
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => cancelled = e.HoldingState == HoldingState.Cancelled);
_mouse.Down(border);
Assert.False(cancelled);
var timer = Assert.Single(scheduledTimers);
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
timer.action();
var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests());
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.Interval);
timer.ForceFire();
var secondMouse = new MouseTestHelper();
@ -333,9 +328,7 @@ namespace Avalonia.Base.UnitTests.Input
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(iSettingsMock.Object);
var scheduledTimers = new List<(TimeSpan time, Action action)>();
using var app = UnitTestApplication.Start(new TestServices(
threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
using var app = UnitTestApplication.Start();
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
@ -345,13 +338,13 @@ namespace Avalonia.Base.UnitTests.Input
};
var cancelled = false;
decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled);
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => cancelled = e.HoldingState == HoldingState.Cancelled);
_mouse.Down(border);
var timer = Assert.Single(scheduledTimers);
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
timer.action();
var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests());
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.Interval);
timer.ForceFire();
_mouse.Move(border, position: new Point(3, 3));
@ -371,9 +364,7 @@ namespace Avalonia.Base.UnitTests.Input
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(iSettingsMock.Object);
var scheduledTimers = new List<(TimeSpan time, Action action)>();
using var app = UnitTestApplication.Start(new TestServices(
threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
using var app = UnitTestApplication.Start();
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
@ -383,31 +374,21 @@ namespace Avalonia.Base.UnitTests.Input
};
var raised = false;
decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Completed);
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Completed);
var secondMouse = new MouseTestHelper();
_mouse.Down(border, MouseButton.Left);
// Verify timer duration, but execute it immediately.
var timer = Assert.Single(scheduledTimers);
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
timer.action();
var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests());
Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.Interval);
timer.ForceFire();
secondMouse.Down(border, MouseButton.Left);
Assert.False(raised);
}
private static IPlatformThreadingInterface CreatePlatformThreadingInterface(Action<(TimeSpan, Action)> callback)
{
var threadingInterface = new Mock<IPlatformThreadingInterface>();
threadingInterface.SetupGet(p => p.CurrentThreadIsLoopThread).Returns(true);
threadingInterface.Setup(p => p
.StartTimer(It.IsAny<DispatcherPriority>(), It.IsAny<TimeSpan>(), It.IsAny<Action>()))
.Callback<DispatcherPriority, TimeSpan, Action>((_, t, a) => callback((t, a)));
return threadingInterface.Object;
}
private static void AddHandlers(
Decorator decorator,
@ -415,7 +396,7 @@ namespace Avalonia.Base.UnitTests.Input
IList<string> result,
bool markHandled)
{
decorator.AddHandler(Border.PointerPressedEvent, (s, e) =>
decorator.AddHandler(InputElement.PointerPressedEvent, (_, e) =>
{
result.Add("dp");
@ -425,7 +406,7 @@ namespace Avalonia.Base.UnitTests.Input
}
});
decorator.AddHandler(Border.PointerReleasedEvent, (s, e) =>
decorator.AddHandler(InputElement.PointerReleasedEvent, (_, e) =>
{
result.Add("dr");
@ -435,13 +416,13 @@ namespace Avalonia.Base.UnitTests.Input
}
});
border.AddHandler(Border.PointerPressedEvent, (s, e) => result.Add("bp"));
border.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("br"));
border.AddHandler(InputElement.PointerPressedEvent, (_, _) => result.Add("bp"));
border.AddHandler(InputElement.PointerReleasedEvent, (_, _) => result.Add("br"));
decorator.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("dt"));
decorator.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("ddt"));
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt"));
decorator.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("dt"));
decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("ddt"));
border.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("bt"));
border.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("bdt"));
}
[Fact]
@ -462,7 +443,7 @@ namespace Avalonia.Base.UnitTests.Input
};
var raised = false;
decorator.AddHandler(Gestures.PinchEvent, (s, e) => raised = true);
decorator.AddHandler(Gestures.PinchEvent, (_, _) => raised = true);
var firstPoint = new Point(5, 5);
var secondPoint = new Point(10, 10);
@ -490,7 +471,7 @@ namespace Avalonia.Base.UnitTests.Input
};
var raised = false;
decorator.AddHandler(Gestures.PinchEvent, (s, e) => raised = true);
decorator.AddHandler(Gestures.PinchEvent, (_, _) => raised = true);
var firstPoint = new Point(5, 5);
var secondPoint = new Point(10, 10);
@ -526,7 +507,7 @@ namespace Avalonia.Base.UnitTests.Input
};
var raised = false;
decorator.AddHandler(Gestures.ScrollGestureEvent, (s, e) => raised = true);
decorator.AddHandler(Gestures.ScrollGestureEvent, (_, _) => raised = true);
var firstTouch = new TouchTestHelper();

25
tests/Avalonia.Controls.UnitTests/ToolTipTests.cs

@ -4,6 +4,7 @@ using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using Moq;
using Xunit;
@ -157,24 +158,10 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact(Skip = "Timers should NOT, in fact, be checked via IPlatformThreadingInterface")]
[Fact]
public void Should_Open_On_Pointer_Enter_With_Delay()
{
Action timercallback = null;
var delay = TimeSpan.Zero;
var pti = Mock.Of<IPlatformThreadingInterface>(x => x.CurrentThreadIsLoopThread == true);
Mock.Get(pti)
.Setup(v => v.StartTimer(It.IsAny<DispatcherPriority>(), It.IsAny<TimeSpan>(), It.IsAny<Action>()))
.Callback<DispatcherPriority, TimeSpan, Action>((priority, interval, tick) =>
{
delay = interval;
timercallback = tick;
})
.Returns(Disposable.Empty);
using (UnitTestApplication.Start(TestServices.StyledWindow.With(threadingInterface: pti)))
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
@ -194,11 +181,11 @@ namespace Avalonia.Controls.UnitTests
_mouseHelper.Enter(target);
Assert.Equal(TimeSpan.FromMilliseconds(1), delay);
Assert.NotNull(timercallback);
var timer = Assert.Single(Dispatcher.SnapshotTimersForUnitTests());
Assert.Equal(TimeSpan.FromMilliseconds(1), timer.Interval);
Assert.False(ToolTip.GetIsOpen(target));
timercallback();
timer.ForceFire();
Assert.True(ToolTip.GetIsOpen(target));
}

Loading…
Cancel
Save