Skip to content

Commit 81810a9

Browse files
committed
Feat: Implement IDebouncer interfaces for standardization
1 parent dad311a commit 81810a9

File tree

8 files changed

+91
-15
lines changed

8 files changed

+91
-15
lines changed

src/CodeOfChaos.Extensions.AspNetCore.Components/EventCallbacks/EventCallbackDebouncer.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ namespace CodeOfChaos.Extensions.Debouncers;
99
// Code
1010
// ---------------------------------------------------------------------------------------------------------------------
1111
// ReSharper disable once ConvertToAutoPropertyWithPrivateSetter
12-
public sealed class EventCallbackDebouncer : DebouncerBase<EventCallbackDebouncer.EmptyUnit> {
12+
public sealed class EventCallbackDebouncer : DebouncerBase<EventCallbackDebouncer.EmptyUnit>, IDebouncer {
1313
public readonly struct EmptyUnit;
1414
private EventCallback Callback { get; init; }
1515

1616
private bool _isEmpty;
17-
protected override bool IsEmpty => _isEmpty;
17+
public override bool IsEmpty => _isEmpty;
1818
public static EventCallbackDebouncer Empty => new() {
1919
Callback = default,
2020
_isEmpty = true
@@ -42,11 +42,11 @@ protected async override ValueTask InvokeCallbackAsync(EmptyUnit item, Cancellat
4242
}
4343

4444
// ReSharper disable once ConvertToAutoPropertyWithPrivateSetter
45-
public sealed class EventCallbackDebouncer<T> : DebouncerBase<T> {
45+
public sealed class EventCallbackDebouncer<T> : DebouncerBase<T>, IDebouncer, IDebouncer<T> {
4646
private EventCallback<T> Callback { get; init; }
4747

4848
private bool _isEmpty;
49-
protected override bool IsEmpty => _isEmpty;
49+
public override bool IsEmpty => _isEmpty;
5050
public static EventCallbackDebouncer<T> Empty => new() {
5151
Callback = default,
5252
_isEmpty = true
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
namespace CodeOfChaos.Extensions.Debouncers;
5+
6+
// ---------------------------------------------------------------------------------------------------------------------
7+
// Code
8+
// ---------------------------------------------------------------------------------------------------------------------
9+
public interface IDebouncerBase {
10+
bool IsEmpty { get; }
11+
}
12+
13+
public interface IDebouncer : IDebouncerBase {
14+
Task InvokeDebouncedAsync(CancellationToken ct = default);
15+
}
16+
17+
public interface IDebouncer<in T> : IDebouncerBase {
18+
Task InvokeDebouncedAsync(T item, CancellationToken ct = default);
19+
}

src/CodeOfChaos.Extensions/Debouncers/Regular/Debouncer.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ namespace CodeOfChaos.Extensions.Debouncers;
66
// ---------------------------------------------------------------------------------------------------------------------
77
// Code
88
// ---------------------------------------------------------------------------------------------------------------------
9-
public sealed class Debouncer: DebouncerBase<Debouncer.EmptyUnit> {
9+
public sealed class Debouncer: DebouncerBase<Debouncer.EmptyUnit>, IDebouncer {
1010
public readonly struct EmptyUnit; // Workaround for reusing DebouncerBase<T> instead of creating a fully new DebouncerBase without generics
1111
private object? Callback { get; init; }
12-
protected override bool IsEmpty => Callback is null;
12+
public override bool IsEmpty => Callback is null;
1313

1414
public static Debouncer Empty => new() {
1515
Callback = null
@@ -68,9 +68,9 @@ protected async override ValueTask InvokeCallbackAsync(EmptyUnit item, Cancellat
6868
}
6969
}
7070

71-
public sealed class Debouncer<T> : DebouncerBase<T> {
71+
public sealed class Debouncer<T> : DebouncerBase<T>, IDebouncer<T> {
7272
private object? Callback { get; init; }
73-
protected override bool IsEmpty => Callback is null;
73+
public override bool IsEmpty => Callback is null;
7474

7575
public static Debouncer<T> Empty => new() {
7676
Callback = null

src/CodeOfChaos.Extensions/Debouncers/Regular/DebouncerBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public abstract class DebouncerBase<T> : IAsyncDisposable {
1616
private bool _isDisposed;
1717
private T? _latestValue;
1818

19-
protected abstract bool IsEmpty { get; }
19+
public abstract bool IsEmpty { get; }
2020

2121
// -----------------------------------------------------------------------------------------------------------------
2222
// Methods

src/CodeOfChaos.Extensions/Debouncers/Throttled/ThrottledDebouncer.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ namespace CodeOfChaos.Extensions.Debouncers;
66
// ---------------------------------------------------------------------------------------------------------------------
77
// Code
88
// ---------------------------------------------------------------------------------------------------------------------
9-
public sealed class ThrottledDebouncer: ThrottledDebouncerBase<ThrottledDebouncer.EmptyUnit> {
9+
public sealed class ThrottledDebouncer: ThrottledDebouncerBase<ThrottledDebouncer.EmptyUnit>, IDebouncer {
1010
public readonly struct EmptyUnit; // Workaround for reusing ThrottledDebouncerBase<T> instead of creating a fully new DebouncerBase without generics
1111
private object? Callback { get; init; }
12-
13-
protected override bool IsEmpty => Callback is null;
12+
public override bool IsEmpty => Callback is null;
13+
1414
public static ThrottledDebouncer Empty => new() {
1515
Callback = null
1616
};
@@ -71,9 +71,10 @@ protected async override ValueTask InvokeCallbackAsync(EmptyUnit item, Cancellat
7171
}
7272
}
7373

74-
public sealed class ThrottledDebouncer<T> : ThrottledDebouncerBase<T> {
74+
public sealed class ThrottledDebouncer<T> : ThrottledDebouncerBase<T>, IDebouncer<T> {
7575
private object? Callback { get; init; }
76-
protected override bool IsEmpty => Callback is null;
76+
public override bool IsEmpty => Callback is null;
77+
7778
public static ThrottledDebouncer<T> Empty => new() {
7879
Callback = null
7980
};

src/CodeOfChaos.Extensions/Debouncers/Throttled/ThrottledDebouncerBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public abstract class ThrottledDebouncerBase<T> : IAsyncDisposable {
2121
private DateTime _lastExecuteTime = DateTime.MinValue;
2222
private DateTime _firstCallTime = DateTime.MinValue;
2323

24-
protected abstract bool IsEmpty { get; }
24+
public abstract bool IsEmpty { get; }
2525

2626
// -----------------------------------------------------------------------------------------------------------------
2727
// Methods

tests/Tests.CodeOfChaos.Extensions.AspNetCore.Components/EventCallbacks/EventCallbackDebouncerTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,25 @@ namespace Tests.CodeOfChaos.Extensions.AspNetCore.Components.EventCallbacks;
99
// Code
1010
// ---------------------------------------------------------------------------------------------------------------------
1111
public class EventCallbackDebouncerTests {
12+
public static IEnumerable<Func<(IDebouncerBase, bool)>> GetEmptyDebouncers() {
13+
yield return () => (EventCallbackDebouncer.Empty, true);
14+
yield return () => (EventCallbackDebouncer<string>.Empty, true);
15+
yield return () => (EventCallbackDebouncer<int>.Empty, true);
16+
yield return () => (EventCallbackDebouncer.FromEventCallback(EventCallback.Factory.Create(string.Empty, callback: () => Task.CompletedTask)), false);
17+
yield return () => (EventCallbackDebouncer<string>.FromEventCallback(EventCallback.Factory.Create<string>(string.Empty, callback: () => Task.CompletedTask)), false);
18+
yield return () => (EventCallbackDebouncer<int>.FromEventCallback(EventCallback.Factory.Create<int>(string.Empty, callback: () => Task.CompletedTask)), false);
19+
}
20+
21+
[Test]
22+
[MethodDataSource(nameof(GetEmptyDebouncers))]
23+
public async Task Empty_ShouldBeEmpty(IDebouncerBase debouncer, bool expectedIsEmpty) {
24+
// Arrange & Act
25+
bool result = debouncer.IsEmpty;
26+
27+
// Assert
28+
await Assert.That(result).IsEqualTo(expectedIsEmpty);
29+
}
30+
1231

1332
[Test]
1433
public async Task DefaultDebounceMs_ShouldBe100() {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// ---------------------------------------------------------------------------------------------------------------------
2+
// Imports
3+
// ---------------------------------------------------------------------------------------------------------------------
4+
using CodeOfChaos.Extensions.Debouncers;
5+
6+
namespace Tests.CodeOfChaos.Extensions.Debouncers.Regular;
7+
8+
// ---------------------------------------------------------------------------------------------------------------------
9+
// Code
10+
// ---------------------------------------------------------------------------------------------------------------------
11+
public class DebouncerTests {
12+
public static IEnumerable<Func<(IDebouncerBase, bool)>> GetEmptyDebouncers() {
13+
yield return () => (Debouncer.Empty, true);
14+
yield return () => (Debouncer<string>.Empty, true);
15+
yield return () => (Debouncer<int>.Empty, true);
16+
yield return () => (Debouncer.FromDelegate(() => {}), false);
17+
yield return () => (Debouncer<string>.FromDelegate(_ => {}), false);
18+
yield return () => (Debouncer<int>.FromDelegate(_ => {}), false);
19+
20+
yield return () => (ThrottledDebouncer.Empty, true);
21+
yield return () => (ThrottledDebouncer<string>.Empty, true);
22+
yield return () => (ThrottledDebouncer<int>.Empty, true);
23+
yield return () => (ThrottledDebouncer.FromDelegate(() => {}), false);
24+
yield return () => (ThrottledDebouncer<string>.FromDelegate(_ => {}), false);
25+
yield return () => (ThrottledDebouncer<int>.FromDelegate(_ => {}), false);
26+
}
27+
28+
[Test]
29+
[MethodDataSource(nameof(GetEmptyDebouncers))]
30+
public async Task Empty_ShouldBeEmpty(IDebouncerBase debouncer, bool expectedIsEmpty) {
31+
// Arrange & Act
32+
bool result = debouncer.IsEmpty;
33+
34+
// Assert
35+
await Assert.That(result).IsEqualTo(expectedIsEmpty);
36+
}
37+
}

0 commit comments

Comments
 (0)