diff --git a/Debounce/BufferedEventArgs.cs b/Debounce/BufferedEventArgs.cs
deleted file mode 100644
index 7cc5d16..0000000
--- a/Debounce/BufferedEventArgs.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-FileCopyrightText: 2024 Alain van den Berg
-//
-// SPDX-License-Identifier: MIT
-
-namespace Dorssel.Utilities;
-
-///
-/// Event arguments for the event.
-///
-public class BufferedEventArgs : EventArgs
-{
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// The original data accumulated since the previous buffered event was sent.
- ///
- public BufferedEventArgs(IReadOnlyList bufferedData)
- {
- Buffer = bufferedData;
- }
-
- ///
- /// List of data accumulated in this buffered event.
- ///
- public IReadOnlyList Buffer { get; }
-}
diff --git a/Debounce/Bufferer.cs b/Debounce/Bufferer.cs
deleted file mode 100644
index 97061d8..0000000
--- a/Debounce/Bufferer.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-// SPDX-FileCopyrightText: 2024 Alain van den Berg
-//
-// SPDX-License-Identifier: MIT
-
-using System.Collections.ObjectModel;
-
-namespace Dorssel.Utilities;
-
-///
-/// A that buffers data and sends events with the accumulated data.
-///
-/// Data to buffer per trigger
-///
-/// This is not as performant as the due to allocations.
-///
-public sealed class Bufferer : IDisposable, IDebouncer>
-{
- IDebouncer debouncer;
- List eventList = new();
- object eventListLock = new();
-
- ///
- /// Create Bufferer with the default .
- ///
-#pragma warning disable CA2000 // Dispose objects before losing scope
- public Bufferer() : this(new Debouncer()) { }
-#pragma warning restore CA2000 // Dispose objects before losing scope
-
- ///
- /// Create Bufferer with a specific instance.
- ///
- /// The debouncer instance to use
- /// If debouncer is null
- public Bufferer(IDebouncer debouncer)
- {
- this.debouncer = debouncer ?? throw new ArgumentNullException(nameof(debouncer));
- debouncer.Debounced += Debouncer_Debounced;
- }
-
- ///
- /// Wrap the Debounced event and invoke the Buffered event instead.
- ///
- private void Debouncer_Debounced(object sender, DebouncedEventArgs e)
- {
- IReadOnlyList debouncedEvents;
- lock (eventListLock)
- {
- debouncedEvents = new ReadOnlyCollection(eventList);
- eventList = new();
- }
-
- Debounced?.Invoke(this, new BufferedEventArgs(debouncedEvents));
- }
-
- ///
- public void Trigger(TData? data)
- {
- if (data is not null)
- {
- lock (eventListLock)
- {
- eventList.Add(data);
- }
- }
- debouncer.Trigger();
- }
-
- ///
- public long Reset()
- {
- int count;
- lock (eventListLock)
- {
- count = eventList.Count;
- eventList = new();
- }
- debouncer.Reset();
- return count;
- }
-
- ///
- public TimeSpan DebounceWindow
- {
- get => debouncer.DebounceWindow;
- set => debouncer.DebounceWindow = value;
- }
-
- ///
- public TimeSpan DebounceTimeout
- {
- get => debouncer.DebounceTimeout;
- set => debouncer.DebounceTimeout = value;
- }
-
- ///
- public TimeSpan EventSpacing
- {
- get => debouncer.EventSpacing;
- set => debouncer.EventSpacing = value;
- }
-
- ///
- public TimeSpan HandlerSpacing
- {
- get => debouncer.HandlerSpacing;
- set => debouncer.HandlerSpacing = value;
- }
-
- ///
- public TimeSpan TimingGranularity
- {
- get => debouncer.TimingGranularity;
- set => debouncer.TimingGranularity = value;
- }
-
- ///
- public event EventHandler>? Debounced;
-
- ///
- public void Dispose()
- {
- ((IDisposable)debouncer).Dispose();
- }
-}
diff --git a/Debounce/DebouncedEventArgs.cs b/Debounce/DebouncedEventArgs.cs
index 413e45e..01aeca5 100644
--- a/Debounce/DebouncedEventArgs.cs
+++ b/Debounce/DebouncedEventArgs.cs
@@ -5,31 +5,37 @@
namespace Dorssel.Utilities;
///
-/// Provides data for the event.
+/// Provides data for the event.
///
-public class DebouncedEventArgs : EventArgs
+public class DebouncedEventArgs : EventArgs
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The number of triggers accumulated since the previous event was sent.
/// Must be greater than 0.
///
+ ///
+ /// Accumulated data from each individual trigger, or empty when buffering is disabled in the
+ ///
/// Thrown when is not greater than 0.
- public DebouncedEventArgs(long count)
- : this(count, true)
+ public DebouncedEventArgs(long count, IReadOnlyList triggerData)
+ : this(count, true, triggerData)
{
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The number of triggers accumulated since the previous event was sent.
/// Must be greater than 0 if is true.
///
/// If true, is checked to be within its valid range.
+ ///
+ /// Accumulated data from each individual trigger, or empty when buffering is disabled in the
+ ///
/// Thrown when is true and is not greater than 0.
- protected DebouncedEventArgs(long count, bool boundsCheck)
+ protected DebouncedEventArgs(long count, bool boundsCheck, IReadOnlyList triggerData)
{
if (boundsCheck)
{
@@ -40,6 +46,7 @@ protected DebouncedEventArgs(long count, bool boundsCheck)
}
Count = count;
+ TriggerData = triggerData;
}
///
@@ -49,4 +56,9 @@ protected DebouncedEventArgs(long count, bool boundsCheck)
/// The value will always greater than 0.
///
public long Count { get; }
+
+ ///
+ /// List of data accumulated in this buffered event.
+ ///
+ public IReadOnlyList TriggerData { get; }
}
diff --git a/Debounce/Debouncer.cs b/Debounce/Debouncer.cs
index 5dcd92d..c3b3f16 100644
--- a/Debounce/Debouncer.cs
+++ b/Debounce/Debouncer.cs
@@ -2,24 +2,49 @@
//
// SPDX-License-Identifier: MIT
+using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Dorssel.Utilities;
///
-/// This class implements the interface.
+/// Object which debounces events, i.e., accumulating multiple incoming events into one.
///
-public sealed class Debouncer
- : IDebouncer
- , IDisposable
+public sealed class Debouncer : Debouncer, IDebouncer
{
///
/// Initializes a new instance of the class.
///
public Debouncer()
+ : base(false)
+ {
+ }
+}
+
+///
+/// Object which debounces events, i.e., accumulating multiple incoming events into one with the possibility of
+/// keeping track of the incoming trigger data.
+///
+public class Debouncer : IDebouncer
+ , IDisposable
+{
+ ///
+ /// Initializes a new instance of the class with buffering enabled.
+ ///
+ public Debouncer()
+ : this(true)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with enableBuffering option.
+ ///
+ /// Whether to buffer trigger data or not
+ protected Debouncer(bool enableBuffering)
{
Timer = new Timer(OnTimer, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
+ EnableBuffering = enableBuffering;
}
long InterlockedCountMinusOne = -1;
@@ -53,6 +78,9 @@ internal BenchmarkCounters Benchmark
readonly Timer Timer;
bool TimerActive;
bool SendingEvent;
+ bool EnableBuffering;
+ List TriggerData = new();
+ object TriggerDataLock = new();
internal static long AddWithClamp(long left, long right)
{
@@ -137,6 +165,15 @@ void LockedReschedule()
{
// Sending event now, so accumulate all coalesced triggers.
var count = AddWithClamp(Count, Interlocked.Exchange(ref InterlockedCountMinusOne, -1) + 1);
+ IReadOnlyList triggerData = [];
+ if (EnableBuffering)
+ {
+ lock (TriggerDataLock)
+ {
+ triggerData = new ReadOnlyCollection(TriggerData);
+ TriggerData = new();
+ }
+ }
FirstTrigger.Reset();
LastTrigger.Reset();
@@ -146,7 +183,7 @@ void LockedReschedule()
// Must call handler asynchronously and outside the lock.
Task.Run(() =>
{
- Debounced?.Invoke(this, new DebouncedEventArgs((long)count));
+ Debounced?.Invoke(this, new DebouncedEventArgs((long)count, triggerData));
lock (LockObject)
{
// Handler has finished.
@@ -213,14 +250,18 @@ void LockedReschedule()
#region IDebounce Support
///
- public event EventHandler? Debounced;
-
- ///
- public void Trigger(Void data) => Trigger();
+ public event EventHandler>? Debounced;
///
- public void Trigger()
+ public void Trigger(TData data = default!)
{
+ if (EnableBuffering)
+ {
+ lock (TriggerDataLock)
+ {
+ TriggerData.Add(data);
+ }
+ }
var newCountMinusOne = Interlocked.Increment(ref InterlockedCountMinusOne);
if (newCountMinusOne > 0)
{
@@ -250,6 +291,10 @@ public long Reset()
{
lock (LockObject)
{
+ lock (TriggerDataLock)
+ {
+ TriggerData = new();
+ }
if (!IsDisposed)
{
Count = AddWithClamp(Count, Interlocked.Exchange(ref InterlockedCountMinusOne, -1) + 1);
@@ -356,13 +401,26 @@ void ThrowIfDisposed()
///
public void Dispose()
{
- lock (LockObject)
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Perform object cleanup.
+ ///
+ /// Indicates whether the method call comes from a Dispose method (true) or from a finalizer (false)
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
{
- if (!IsDisposed)
+ lock (LockObject)
{
- Count = AddWithClamp(Count, Interlocked.Exchange(ref InterlockedCountMinusOne, long.MinValue) + 1);
- Timer.Dispose();
- IsDisposed = true;
+ if (!IsDisposed)
+ {
+ Count = AddWithClamp(Count, Interlocked.Exchange(ref InterlockedCountMinusOne, long.MinValue) + 1);
+ Timer.Dispose();
+ IsDisposed = true;
+ }
}
}
}
diff --git a/Debounce/IBufferer.cs b/Debounce/IBufferer.cs
deleted file mode 100644
index b84f965..0000000
--- a/Debounce/IBufferer.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-// SPDX-FileCopyrightText: 2024 Alain van den Berg
-//
-// SPDX-License-Identifier: MIT
-
-namespace Dorssel.Utilities;
-
-///
-/// This interface specifies the public API for the class.
-///
-public interface IBufferer : IDebounceSettings
-{
- ///
- /// This event will be sent when has been called one or more times and
- /// the debounce timer times out and will contain all events that have been triggered.
- ///
- public event EventHandler> Buffered;
-
- /// Accumulates the data of one more trigger.
- /// Data to be buffered.
- /// The object has been disposed.
- public void Trigger(TData data);
-
- /// Resets the accumulated data to an empty collection and cancels any ongoing buffering.
- /// This method may be called even after has been called.
- /// The number of data that had been accumulated since the last event handler was called.
- public long Reset();
-}
diff --git a/Debounce/IDebounceSettings.cs b/Debounce/IDebounceSettings.cs
index c7c4d03..5ed73e1 100644
--- a/Debounce/IDebounceSettings.cs
+++ b/Debounce/IDebounceSettings.cs
@@ -5,7 +5,7 @@
namespace Dorssel.Utilities;
///
-/// Debounce settings for and .
+/// Settings for and .
///
public interface IDebounceSettings
{
diff --git a/Debounce/IDebouncer.cs b/Debounce/IDebouncer.cs
index e2182ba..252cd54 100644
--- a/Debounce/IDebouncer.cs
+++ b/Debounce/IDebouncer.cs
@@ -12,7 +12,7 @@ public struct Void { }
///
/// This interface specifies the public API for the class.
///
-public interface IDebouncer : IDebouncer
+public interface IDebouncer : IDebouncer
{
}
@@ -20,20 +20,18 @@ public interface IDebouncer : IDebouncer
/// Interface for debouncers accumulating triggers and debouncing.
///
/// Data to accumulate when triggering
-/// Type of event arguments for the event
-public interface IDebouncer : IDebounceSettings
- where TEventArgs : EventArgs
+public interface IDebouncer : IDebounceSettings
{
///
/// This event will be sent when has been called one or more times and
/// the debounce timer times out.
///
- public event EventHandler Debounced;
+ public event EventHandler> Debounced;
/// Accumulates one more trigger.
/// Data that accompanies the trigger, or null if no data.
/// The object has been disposed.
- public void Trigger(TData? data = default);
+ public void Trigger(TData data = default!);
/// Resets the accumulated trigger count to 0 and cancels any ongoing debouncing.
/// This method may be called even after has been called.
diff --git a/Examples/BlazorServerPush/Components/Pages/Counter.razor b/Examples/BlazorServerPush/Components/Pages/Counter.razor
index 1b72064..dd6b24a 100644
--- a/Examples/BlazorServerPush/Components/Pages/Counter.razor
+++ b/Examples/BlazorServerPush/Components/Pages/Counter.razor
@@ -36,7 +36,7 @@
Debouncer.Trigger();
}
- void OnDebounced(object? sender, DebouncedEventArgs ev)
+ void OnDebounced(object? sender, DebouncedEventArgs ev)
{
// now it is time to render the new state
InvokeAsync(() => StateHasChanged()).Wait();
diff --git a/Examples/Testable/TestableClass.cs b/Examples/Testable/TestableClass.cs
index 566ec03..f04b5ce 100644
--- a/Examples/Testable/TestableClass.cs
+++ b/Examples/Testable/TestableClass.cs
@@ -4,6 +4,7 @@
using System.Diagnostics;
using Dorssel.Utilities;
+using Void = Dorssel.Utilities.Void;
namespace Testable;
@@ -15,7 +16,7 @@ public TestableClass(IDebouncer debounce)
Debounce.Debounced += OnDebouncedEvents;
}
- void OnDebouncedEvents(object? sender, DebouncedEventArgs debouncedEventArgs)
+ void OnDebouncedEvents(object? sender, DebouncedEventArgs debouncedEventArgs)
{
if (sender == null)
{
diff --git a/Examples/TestableUnitTests/TestableClassTests.cs b/Examples/TestableUnitTests/TestableClassTests.cs
index 289b8db..4ad3f17 100644
--- a/Examples/TestableUnitTests/TestableClassTests.cs
+++ b/Examples/TestableUnitTests/TestableClassTests.cs
@@ -2,9 +2,11 @@
//
// SPDX-License-Identifier: MIT
+using Void = Dorssel.Utilities.Void;
+
namespace TestableUnitTests;
-sealed class MockDebouncedEventArgs(long count) : DebouncedEventArgs(count, false)
+sealed class MockDebouncedEventArgs(long count) : DebouncedEventArgs(count, false, [])
{
}
@@ -16,11 +18,11 @@ public class TestableClassTests
public void ConstructorHappyFlow()
{
var debounce = new Mock();
- debounce.SetupAdd(m => m.Debounced += It.IsAny>());
+ debounce.SetupAdd(m => m.Debounced += It.IsAny>>());
using var _ = new TestableClass(debounce.Object);
- debounce.VerifyAdd(m => m.Debounced += It.IsAny>(), Times.Once());
+ debounce.VerifyAdd(m => m.Debounced += It.IsAny>>(), Times.Once());
}
[TestMethod]
@@ -36,11 +38,11 @@ public void ConstructorThrowsOnNull()
public void DisposeUnregisters()
{
var debounce = new Mock();
- debounce.SetupRemove(m => m.Debounced -= It.IsAny>());
+ debounce.SetupRemove(m => m.Debounced -= It.IsAny>>());
using (new TestableClass(debounce.Object)) { }
- debounce.VerifyRemove(m => m.Debounced -= It.IsAny>(), Times.Once());
+ debounce.VerifyRemove(m => m.Debounced -= It.IsAny>>(), Times.Once());
}
[TestMethod]
@@ -82,7 +84,7 @@ public void HandlerAcceptsNullSender()
using var _ = new TestableClass(debounce.Object);
- debounce.Raise(m => m.Debounced += null, null!, new DebouncedEventArgs(1));
+ debounce.Raise(m => m.Debounced += null, null!, new DebouncedEventArgs(1, []));
}
[TestMethod]
@@ -122,7 +124,7 @@ public void HandlerMaxCount()
using var _ = new TestableClass(debounce.Object);
- debounce.Raise(m => m.Debounced += null, debounce.Object, new DebouncedEventArgs(long.MaxValue));
+ debounce.Raise(m => m.Debounced += null, debounce.Object, new DebouncedEventArgs(long.MaxValue, []));
}
[TestMethod]
@@ -132,6 +134,6 @@ public void HandlerHappyFlow()
using var _ = new TestableClass(debounce.Object);
- debounce.Raise(m => m.Debounced += null, debounce.Object, new DebouncedEventArgs(1));
+ debounce.Raise(m => m.Debounced += null, debounce.Object, new DebouncedEventArgs(1, []));
}
}
diff --git a/PerformanceTests/Program.cs b/PerformanceTests/Program.cs
index e4783d1..062fc52 100644
--- a/PerformanceTests/Program.cs
+++ b/PerformanceTests/Program.cs
@@ -4,6 +4,7 @@
using System.Diagnostics;
using Dorssel.Utilities;
+using Void = Dorssel.Utilities.Void;
namespace PerformanceTests;
@@ -133,7 +134,7 @@ static void Main()
{
Console.WriteLine("Handler speed");
using var debouncer = new Debouncer();
- void handler(object? s, DebouncedEventArgs e)
+ void handler(object? s, DebouncedEventArgs e)
{
// each handler triggers the next
debouncer.Trigger();
@@ -155,7 +156,7 @@ void handler(object? s, DebouncedEventArgs e)
DebounceWindow = TimeSpan.FromTicks(1),
TimingGranularity = TimeSpan.FromTicks(1)
};
- void handler(object? s, DebouncedEventArgs e)
+ void handler(object? s, DebouncedEventArgs e)
{
// each handler triggers the next
debouncer.Trigger();
diff --git a/UnitTests/BuffererTests.cs b/UnitTests/BuffererTests.cs
deleted file mode 100644
index f3ff0ad..0000000
--- a/UnitTests/BuffererTests.cs
+++ /dev/null
@@ -1,347 +0,0 @@
-// SPDX-FileCopyrightText: 2024 Alain van den Berg
-//
-// SPDX-License-Identifier: MIT
-
-namespace UnitTests;
-
-[TestClass]
-[TestCategory("Production")]
-public sealed class BuffererTests : IDisposable
-{
- static TimeSpan TimingUnits(double count) => TimeSpan.FromMilliseconds(50 * count);
-
- static void Sleep(double count) => Thread.Sleep(TimingUnits(count));
-
- Bufferer debouncer;
- List> buffersCaptured = new();
-
- public BuffererTests()
- {
- debouncer = new Bufferer();
- debouncer.Debounced += Debouncer_Buffered;
- }
-
- private void Debouncer_Buffered(object? sender, BufferedEventArgs e)
- {
- buffersCaptured.Add(e.Buffer);
- }
-
- [TestCleanup]
- public void Dispose()
- {
- debouncer.Debounced -= Debouncer_Buffered;
- debouncer.Dispose();
- }
-
- #region Dispose
- [TestMethod]
- public void DisposeNoThrow()
- {
- debouncer.Dispose();
- }
-
- [TestMethod]
- public void DisposeMultipleNoThrow()
- {
- debouncer.Dispose();
- debouncer.Dispose();
- }
- #endregion
-
- #region Trigger
- [TestMethod]
- public void TriggerWithoutHandler()
- {
- debouncer.Trigger(1);
- Sleep(1);
- }
-
- [TestMethod]
- public void TriggerSingle()
- {
- debouncer.Trigger(1);
- Sleep(1);
- Assert.AreEqual(1, buffersCaptured.Count);
- Assert.IsTrue(buffersCaptured[0].SequenceEqual([1]));
- }
-
- [TestMethod]
- public void TriggerSingleDelay()
- {
- debouncer.DebounceWindow = TimingUnits(2);
- debouncer.Trigger(1);
- Sleep(1);
- Assert.AreEqual(0, buffersCaptured.Count);
- Sleep(2);
- Assert.AreEqual(1, buffersCaptured.Count);
- Assert.IsTrue(buffersCaptured[0].SequenceEqual([1]));
- }
-
- [TestMethod]
- public void TriggersWithTimeout()
- {
- debouncer.DebounceWindow = TimingUnits(2);
- debouncer.DebounceTimeout = TimingUnits(4);
-
- for (var i = 0; i < 6; ++i)
- {
- debouncer.Trigger(i);
- Sleep(1);
- }
- Assert.AreEqual(1, buffersCaptured.Count);
- Assert.IsTrue(buffersCaptured.Last().SequenceEqual([0, 1, 2, 3]), $"Was: [{string.Join(",",buffersCaptured.Last())}]");
-
- Sleep(2);
- Assert.AreEqual(2, buffersCaptured.Count);
- Assert.IsTrue(buffersCaptured.Last().SequenceEqual([4, 5]), $"Was: [{string.Join(",", buffersCaptured.Last())}]");
- }
-
- [TestMethod]
- public void TriggerCoalescence()
- {
- debouncer.DebounceWindow = TimingUnits(1);
- debouncer.TimingGranularity = TimingUnits(1);
- List expectedEvents = new();
- for (var i = 0; i < 10; ++i)
- {
- debouncer.Trigger(i);
- expectedEvents.Add(i);
- }
- Sleep(4);
-
- Assert.AreEqual(1, buffersCaptured.Count);
- Assert.IsTrue(buffersCaptured.Last().SequenceEqual(expectedEvents), $"Was: [{string.Join(",", buffersCaptured.Last())}]");
- }
-
- [TestMethod]
- public void TriggerDuringHandlerSpacing()
- {
- debouncer.HandlerSpacing = TimingUnits(3);
-
- debouncer.Trigger(1);
- Sleep(1);
- Assert.AreEqual(1, buffersCaptured.Count);
- Assert.IsTrue(buffersCaptured.Last().SequenceEqual([1]));
- debouncer.Trigger(2);
- Sleep(1);
- Assert.AreEqual(1, buffersCaptured.Count);
- Sleep(2);
- Assert.AreEqual(2, buffersCaptured.Count);
- Assert.IsTrue(buffersCaptured.Last().SequenceEqual([2]));
- }
- #endregion
-
- #region Reset
- [TestMethod]
- public void ResetWhileIdle()
- {
- Assert.AreEqual(0L, debouncer.Reset());
- }
-
- [TestMethod]
- public void ResetAfterDispose()
- {
- debouncer.Dispose();
- Assert.AreEqual(0L, debouncer.Reset());
- }
-
- [TestMethod]
- public void ResetDuringDebounce()
- {
- debouncer.DebounceWindow = TimingUnits(1);
-
- debouncer.Trigger(1);
- Assert.AreEqual(1L, debouncer.Reset());
- Sleep(2);
- Assert.AreEqual(0L, debouncer.Reset());
- }
- #endregion
-
- [TestMethod]
- public void ConstructorWithNullDebouncer()
- {
- Assert.ThrowsException(() => new Bufferer(null!));
- }
-
- #region DebounceTimeout
- [TestMethod]
- public void DebounceTimeoutDefault()
- {
- Assert.AreEqual(Timeout.InfiniteTimeSpan, debouncer.DebounceTimeout);
- }
-
- [TestMethod]
- [DynamicData(nameof(TimeSpanData.NonNegative), typeof(TimeSpanData))]
- [DynamicData(nameof(TimeSpanData.Infinite), typeof(TimeSpanData))]
- public void DebounceTimeoutValid(TimeSpan debounceTimeout)
- {
- debouncer.DebounceTimeout = TimeSpanData.ArbitraryNonDefault;
-
- debouncer.DebounceTimeout = debounceTimeout;
- Assert.AreEqual(debounceTimeout, debouncer.DebounceTimeout);
- }
-
- [TestMethod]
- [DynamicData(nameof(TimeSpanData.Negative), typeof(TimeSpanData))]
- public void DebounceTimeoutInvalid(TimeSpan debounceTimeout)
- {
- debouncer.DebounceTimeout = TimeSpan.FromMilliseconds(1);
- Assert.ThrowsException(() => debouncer.DebounceTimeout = debounceTimeout);
- Assert.AreEqual(TimeSpan.FromMilliseconds(1), debouncer.DebounceTimeout);
- }
-
- [TestMethod]
- public void DebounceTimeoutUnchanged()
- {
- debouncer.DebounceTimeout = TimeSpan.FromMilliseconds(1);
- Assert.AreEqual(TimeSpan.FromMilliseconds(1), debouncer.DebounceTimeout);
- debouncer.DebounceTimeout = TimeSpan.FromMilliseconds(1);
- Assert.AreEqual(TimeSpan.FromMilliseconds(1), debouncer.DebounceTimeout);
- }
-
- [TestMethod]
- public void DebounceTimeoutAfterDispose()
- {
- var debouncer = new Bufferer();
- debouncer.Dispose();
- Assert.ThrowsException(() => debouncer.DebounceTimeout = TimeSpan.Zero);
- }
- #endregion
-
- #region EventSpacing
- [TestMethod]
- public void EventSpacingDefault()
- {
- Assert.AreEqual(TimeSpan.Zero, debouncer.EventSpacing);
- }
-
- [TestMethod]
- [DynamicData(nameof(TimeSpanData.NonNegative), typeof(TimeSpanData))]
- public void EventSpacingValid(TimeSpan eventSpacing)
- {
- debouncer.EventSpacing = TimeSpanData.ArbitraryNonDefault;
-
- debouncer.EventSpacing = eventSpacing;
- Assert.AreEqual(eventSpacing, debouncer.EventSpacing);
- }
-
- [TestMethod]
- [DynamicData(nameof(TimeSpanData.Negative), typeof(TimeSpanData))]
- [DynamicData(nameof(TimeSpanData.Infinite), typeof(TimeSpanData))]
- public void EventSpacingInvalid(TimeSpan eventSpacing)
- {
- debouncer.EventSpacing = TimeSpan.FromMilliseconds(1);
- Assert.ThrowsException(() => debouncer.EventSpacing = eventSpacing);
- Assert.AreEqual(TimeSpan.FromMilliseconds(1), debouncer.EventSpacing);
- }
-
- [TestMethod]
- public void EventSpacingUnchanged()
- {
- debouncer.EventSpacing = TimeSpan.FromMilliseconds(1);
- Assert.AreEqual(TimeSpan.FromMilliseconds(1), debouncer.EventSpacing);
- debouncer.EventSpacing = TimeSpan.FromMilliseconds(1);
- Assert.AreEqual(TimeSpan.FromMilliseconds(1), debouncer.EventSpacing);
- }
-
- [TestMethod]
- public void EventSpacingAfterDispose()
- {
- debouncer.Dispose();
- Assert.ThrowsException(() => debouncer.EventSpacing = TimeSpan.Zero);
- }
- #endregion
-
- #region HandlerSpacing
- [TestMethod]
- public void HandlerSpacingDefault()
- {
- Assert.AreEqual(TimeSpan.Zero, debouncer.HandlerSpacing);
- }
-
- [TestMethod]
- [DynamicData(nameof(TimeSpanData.NonNegative), typeof(TimeSpanData))]
- public void HandlerSpacingValid(TimeSpan HandlerSpacing)
- {
- debouncer.HandlerSpacing = TimeSpanData.ArbitraryNonDefault;
-
- debouncer.HandlerSpacing = HandlerSpacing;
- Assert.AreEqual(HandlerSpacing, debouncer.HandlerSpacing);
- }
-
- [TestMethod]
- [DynamicData(nameof(TimeSpanData.Negative), typeof(TimeSpanData))]
- [DynamicData(nameof(TimeSpanData.Infinite), typeof(TimeSpanData))]
- public void HandlerSpacingInvalid(TimeSpan HandlerSpacing)
- {
- debouncer.HandlerSpacing = TimeSpan.FromMilliseconds(1);
- Assert.ThrowsException(() => debouncer.HandlerSpacing = HandlerSpacing);
- Assert.AreEqual(TimeSpan.FromMilliseconds(1), debouncer.HandlerSpacing);
- }
-
- [TestMethod]
- public void HandlerSpacingUnchanged()
- {
- debouncer.HandlerSpacing = TimeSpan.FromMilliseconds(1);
- Assert.AreEqual(TimeSpan.FromMilliseconds(1), debouncer.HandlerSpacing);
- debouncer.HandlerSpacing = TimeSpan.FromMilliseconds(1);
- Assert.AreEqual(TimeSpan.FromMilliseconds(1), debouncer.HandlerSpacing);
- }
-
- [TestMethod]
- public void HandlerSpacingAfterDispose()
- {
- debouncer.Dispose();
- Assert.ThrowsException(() => debouncer.HandlerSpacing = TimeSpan.Zero);
- }
- #endregion
-
- #region TimingGranularity
- [TestMethod]
- public void TimingGranularityDefault()
- {
- Assert.AreEqual(TimeSpan.Zero, debouncer.TimingGranularity);
- }
-
- [TestMethod]
- [DynamicData(nameof(TimeSpanData.NonNegative), typeof(TimeSpanData))]
- public void TimingGranularityValid(TimeSpan timingGranularity)
- {
- debouncer.DebounceWindow = TimeSpan.MaxValue;
- debouncer.TimingGranularity = TimeSpanData.ArbitraryNonDefault;
-
- debouncer.TimingGranularity = timingGranularity;
- Assert.AreEqual(timingGranularity, debouncer.TimingGranularity);
- }
-
- [TestMethod]
- [DynamicData(nameof(TimeSpanData.Negative), typeof(TimeSpanData))]
- [DynamicData(nameof(TimeSpanData.Infinite), typeof(TimeSpanData))]
- public void TimingGranularityInvalid(TimeSpan timingGranularity)
- {
- debouncer.DebounceWindow = TimeSpan.MaxValue;
- debouncer.TimingGranularity = TimeSpan.FromMilliseconds(2);
-
- Assert.ThrowsException(() => debouncer.TimingGranularity = timingGranularity);
- Assert.AreEqual(TimeSpan.FromMilliseconds(2), debouncer.TimingGranularity);
- }
-
- [TestMethod]
- public void TimingGranularityUnchanged()
- {
- debouncer.DebounceWindow = TimeSpan.MaxValue;
- debouncer.TimingGranularity = TimeSpan.FromMilliseconds(2);
-
- Assert.AreEqual(TimeSpan.FromMilliseconds(2), debouncer.TimingGranularity);
- debouncer.TimingGranularity = TimeSpan.FromMilliseconds(2);
- Assert.AreEqual(TimeSpan.FromMilliseconds(2), debouncer.TimingGranularity);
- }
-
- [TestMethod]
- public void TimingGranularityAfterDispose()
- {
- debouncer.Dispose();
- Assert.ThrowsException(() => debouncer.TimingGranularity = TimeSpan.Zero);
- }
- #endregion
-}
diff --git a/UnitTests/DebouncedEventArgsTest.cs b/UnitTests/DebouncedEventArgsTest.cs
index 45a717d..52367f6 100644
--- a/UnitTests/DebouncedEventArgsTest.cs
+++ b/UnitTests/DebouncedEventArgsTest.cs
@@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: MIT
+using Void = Dorssel.Utilities.Void;
+
namespace UnitTests;
[TestClass]
@@ -34,7 +36,7 @@ public static IEnumerable