From 968e61f649270f9f8a7f8db517cd755bdd756132 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 17 Mar 2023 17:48:31 +0600 Subject: [PATCH] Fixed tests, reintroduced Post with SendOrPostCallback, removed JobRunner --- .../Threading/Dispatcher.Invoke.cs | 12 + .../Threading/Dispatcher.Queue.cs | 16 + .../Threading/Dispatcher.Timers.cs | 4 + src/Avalonia.Base/Threading/Dispatcher.cs | 6 +- .../Threading/DispatcherOperation.cs | 39 +++ src/Avalonia.Base/Threading/JobRunner.cs | 300 ------------------ .../Utilities/DispatcherTimerHelper.cs | 17 + .../Input/GesturesTests.cs | 127 ++++---- .../ToolTipTests.cs | 25 +- 9 files changed, 151 insertions(+), 395 deletions(-) delete mode 100644 src/Avalonia.Base/Threading/JobRunner.cs create mode 100644 src/Avalonia.Base/Utilities/DispatcherTimerHelper.cs diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs index e906931736..80b62b3818 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs +++ b/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); } + + /// + /// Posts an action that will be invoked on the dispatcher thread. + /// + /// The method. + /// The argument of method to call. + /// The priority with which to invoke the method. + 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); + } } \ No newline at end of file diff --git a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs index bf34e80b61..0ce2479a45 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs +++ b/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"); diff --git a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs index 07c063ee31..e71e7375d3 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs +++ b/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 SnapshotTimersForUnitTests() => + s_uiThread!._timers.Where(t => t != s_uiThread._backgroundTimer).ToList(); } \ No newline at end of file diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index 14808f00a1..9a0dacfbb8 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -16,13 +16,13 @@ namespace Avalonia.Threading; /// 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) { diff --git a/src/Avalonia.Base/Threading/DispatcherOperation.cs b/src/Avalonia.Base/Threading/DispatcherOperation.cs index 5d2eab5a59..b0d7e5a049 100644 --- a/src/Avalonia.Base/Threading/DispatcherOperation.cs +++ b/src/Avalonia.Base/Threading/DispatcherOperation.cs @@ -261,6 +261,45 @@ public class DispatcherOperation : 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 tcs) + tcs.SetResult(null); + } + } + catch (Exception e) + { + lock (Dispatcher.InstanceLock) + { + Status = DispatcherOperationStatus.Completed; + if (TaskSource is TaskCompletionSource tcs) + tcs.SetException(e); + } + + if (ThrowOnUiThread) + throw; + } + } +} + public enum DispatcherOperationStatus { Pending = 0, diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs deleted file mode 100644 index b2b4f876d1..0000000000 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ /dev/null @@ -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 -{ - /// - /// A main loop in a . - /// - internal class JobRunner - { - private IPlatformThreadingInterface? _platform; - - private readonly Queue[] _queues = Enumerable.Range(0, (int)DispatcherPriority.MaxValue + 1) - .Select(_ => new Queue()).ToArray(); - - public JobRunner(IPlatformThreadingInterface? platform) - { - _platform = platform; - } - - /// - /// Runs continuations pushed on the loop. - /// - /// Priority to execute jobs for. Pass null if platform doesn't have internal priority system - public void RunJobs(DispatcherPriority? priority) - { - var minimumPriority = priority ?? DispatcherPriority.MinimumActiveValue; - while (true) - { - var job = GetNextJob(minimumPriority); - if (job == null) - return; - - job.Run(); - } - } - - /// - /// Invokes a method on the main loop. - /// - /// The method. - /// The priority with which to invoke the method. - /// A task that can be used to track the method's execution. - public Task InvokeAsync(Action action, DispatcherPriority priority) - { - var job = new Job(action, priority, false); - AddJob(job); - return job.Task!; - } - - /// - /// Invokes a method on the main loop. - /// - /// The method. - /// The priority with which to invoke the method. - /// A task that can be used to track the method's execution. - public Task InvokeAsync(Func function, DispatcherPriority priority) - { - var job = new JobWithResult(function, priority); - AddJob(job); - return job.Task; - } - - /// - /// Post action that will be invoked on main thread - /// - /// The method. - /// - /// The priority with which to invoke the method. - internal void Post(Action action, DispatcherPriority priority) - { - AddJob(new Job(action, priority, true)); - } - - /// - /// Post action that will be invoked on main thread - /// - /// The method to call. - /// The parameter of method to call. - /// The priority with which to invoke the method. - internal void Post(SendOrPostCallback action, object? parameter, DispatcherPriority priority) - { - AddJob(new JobWithArg(action, parameter, priority, true)); - } - - /// - /// Allows unit tests to change the platform threading interface. - /// - internal void UpdateServices() - { - _platform = AvaloniaLocator.Current.GetService(); - } - - 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 - { - /// - /// Gets the job priority. - /// - DispatcherPriority Priority { get; } - - /// - /// Runs the job. - /// - void Run(); - } - - /// - /// A job to run. - /// - private sealed class Job : IJob - { - /// - /// The method to call. - /// - private readonly Action _action; - /// - /// The task completion source. - /// - private readonly TaskCompletionSource? _taskCompletionSource; - - /// - /// Initializes a new instance of the class. - /// - /// The method to call. - /// The job priority. - /// Do not wrap exception in TaskCompletionSource - public Job(Action action, DispatcherPriority priority, bool throwOnUiThread) - { - _action = action; - Priority = priority; - _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); - } - - /// - public DispatcherPriority Priority { get; } - - /// - /// The task. - /// - public Task? Task => _taskCompletionSource?.Task; - - /// - void IJob.Run() - { - if (_taskCompletionSource == null) - { - _action(); - return; - } - try - { - _action(); - _taskCompletionSource.SetResult(null); - } - catch (Exception e) - { - _taskCompletionSource.SetException(e); - } - } - } - - /// - /// A typed job to run. - /// - private sealed class JobWithArg : IJob - { - private readonly SendOrPostCallback _action; - private readonly object? _parameter; - private readonly TaskCompletionSource? _taskCompletionSource; - - /// - /// Initializes a new instance of the class. - /// - /// The method to call. - /// The parameter of method to call. - /// The job priority. - /// Do not wrap exception in TaskCompletionSource - - public JobWithArg(SendOrPostCallback action, object? parameter, DispatcherPriority priority, bool throwOnUiThread) - { - _action = action; - _parameter = parameter; - Priority = priority; - _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); - } - - /// - public DispatcherPriority Priority { get; } - - /// - void IJob.Run() - { - if (_taskCompletionSource == null) - { - _action(_parameter); - return; - } - try - { - _action(_parameter); - _taskCompletionSource.SetResult(default); - } - catch (Exception e) - { - _taskCompletionSource.SetException(e); - } - } - } - - /// - /// A job to run thath return value. - /// - /// Type of job result - private sealed class JobWithResult : IJob - { - private readonly Func _function; - private readonly TaskCompletionSource _taskCompletionSource; - - /// - /// Initializes a new instance of the class. - /// - /// The method to call. - /// The job priority. - public JobWithResult(Func function, DispatcherPriority priority) - { - _function = function; - Priority = priority; - _taskCompletionSource = new TaskCompletionSource(); - } - - /// - public DispatcherPriority Priority { get; } - - /// - /// The task. - /// - public Task Task => _taskCompletionSource.Task; - - /// - void IJob.Run() - { - try - { - var result = _function(); - _taskCompletionSource.SetResult(result); - } - catch (Exception e) - { - _taskCompletionSource.SetException(e); - } - } - } - } -} diff --git a/src/Avalonia.Base/Utilities/DispatcherTimerHelper.cs b/src/Avalonia.Base/Utilities/DispatcherTimerHelper.cs new file mode 100644 index 0000000000..0c4a5f1051 --- /dev/null +++ b/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(); + } +} \ No newline at end of file diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index d466f6a4a5..c53891fa1c 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/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())).Returns(new Size(16, 16)); AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().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().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().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().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().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().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(); - threadingInterface.SetupGet(p => p.CurrentThreadIsLoopThread).Returns(true); - threadingInterface.Setup(p => p - .StartTimer(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((_, t, a) => callback((t, a))); - return threadingInterface.Object; - } private static void AddHandlers( Decorator decorator, @@ -415,7 +396,7 @@ namespace Avalonia.Base.UnitTests.Input IList 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(); diff --git a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs b/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs index c268fed28c..74e506105d 100644 --- a/tests/Avalonia.Controls.UnitTests/ToolTipTests.cs +++ b/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(x => x.CurrentThreadIsLoopThread == true); - - Mock.Get(pti) - .Setup(v => v.StartTimer(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((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)); }