Skip to content

Comments

Add TimeProvider support to DebounceDispatcher and ThrottleDispatcher#12435

Merged
ScarletKuro merged 7 commits intodevfrom
copilot/make-debounce-throttle-time-provider-aware
Jan 13, 2026
Merged

Add TimeProvider support to DebounceDispatcher and ThrottleDispatcher#12435
ScarletKuro merged 7 commits intodevfrom
copilot/make-debounce-throttle-time-provider-aware

Conversation

Copy link
Contributor

Copilot AI commented Jan 13, 2026

Plan: Make DebounceDispatcher/ThrottleDispatcher TimeProvider-aware

  • Update DebounceDispatcher to accept TimeProvider parameter
    • Add optional TimeProvider constructor parameter (defaults to TimeProvider.System)
    • Replace DateTime.UtcNow with timeProvider.GetUtcNow()
    • Replace Task.Delay with timeProvider.Delay (using TimeProviderTaskExtensions)
  • Update ThrottleDispatcher to accept TimeProvider parameter
    • Add optional TimeProvider constructor parameter (defaults to TimeProvider.System)
    • Replace DateTime.UtcNow with timeProvider.GetUtcNow()
  • Update components using dispatchers to pass TimeProvider from DI
    • Update MudDebouncedInput to inject and pass TimeProvider
    • Update MudTabs to inject and pass TimeProvider (using Lazy for safe initialization)
    • Update MudColorPicker to inject and pass TimeProvider
  • Update tests to use FakeTimeProvider
    • Update DebounceDispatcherTests to use FakeTimeProvider
    • Update ThrottleDispatcherTests to use FakeTimeProvider
  • Replace optional parameters with constructor overloads (cleaner API)
  • Run tests and validate changes
  • Code review and security scan
Original prompt

This section details on the original issue you should resolve

<issue_title>Make DebounceDispatcher/ThrottleDispatcher time-provider-aware</issue_title>
<issue_description>## Context

Right now the only place that already supports TimeProvider is the DatePicker (MudBaseDatePicker/MudDatePicker), and a few DatePicker tests use FakeTimeProvider. Most of the timing-sensitive utilities (debounce/throttle) and many component tests still rely on DateTime.UtcNow plus Task.Delay. So using FakeTimeProvider throughout our tests would require the production code to accept a TimeProvider and use its clock/timers. Once that’s in place, the tests can advance fake time instead of sleeping.

Issue

Debounce/Throttle utilities use DateTime.UtcNow and Task.Delay, so tests cannot be driven by FakeTimeProvider.
These are the core utilities behind many tests currently using Task.Delay (e.g., DebounceDispatcherTests, ThrottleDispatcherTests, TextFieldTests, NumericFieldTests, FormTests, etc.). To make fake time effective, these utilities need to take a TimeProvider and use it for both “now” and waiting.

Proposal

Update src/MudBlazor/Utilities/Debounce/DebounceDispatcher.cs and src/MudBlazor/Utilities/Throttle/ThrottleDispatcher.cs to accept a TimeProvider (constructor parameter with TimeProvider.System default). Replace DateTime.UtcNow with timeProvider.GetUtcNow() and replace Task.Delay(_interval, token) with time-provider-based delay (e.g., timeProvider.CreateTimer or TimeProviderExtensions.Delay). Ensure callers in MudDebouncedInput, chart bases, MudTabs, MudColorPicker, etc. pass through a TimeProvider from DI so the same fake clock can be used in tests.

Target tests that currently sleep to cross debounce/throttle/hover intervals, such as:

  • src/MudBlazor.UnitTests/Utilities/Debounce/DebounceDispatcherTests.cs
  • src/MudBlazor.UnitTests/Utilities/Throttle/ThrottleDispatcherTests.cs
  • component tests with debounce/hover timing (e.g., TextFieldTests.cs, NumericFieldTests.cs, FormTests.cs, MenuTests.cs, AutocompleteTests.cs, SelectTests.cs)
    Inject/register FakeTimeProvider into the test DI container (similar to DatePickerTests), advance time via timeProvider.Advance(TimeSpan.FromMilliseconds(...)), and await the action tasks instead of sleeping.

What it would look like

What it would look like in tests (illustrative)

Once DebounceDispatcher uses a TimeProvider, a test like DebounceAsync_MultipleCallsOutsideInterval_ExecutesMultipleTimes could conceptually become:

var timeProvider = new FakeTimeProvider();
using var debounce = new DebounceDispatcher(100, timeProvider);
var counter = 0;

await debounce.DebounceAsync(() => { counter++; return Task.CompletedTask; });
timeProvider.Advance(TimeSpan.FromMilliseconds(150));
await debounce.DebounceAsync(() => { counter++; return Task.CompletedTask; });
timeProvider.Advance(TimeSpan.FromMilliseconds(150));
await debounce.DebounceAsync(() => { counter++; return Task.CompletedTask; });

counter.Should().Be(3);

That same pattern would replace many of the Task.Delay(...) usages across tests that are currently waiting for debounce/throttle intervals, hover delays, or visible-duration timers—provided the underlying implementation uses TimeProvider.
</issue_description>

Comments on the Issue (you are @copilot in this section)

@danielchalmers @ScarletKuro What do you think? Does this seem reasonable? @ScarletKuro > [@ScarletKuro](https://github.com/ScarletKuro) What do you think? Does this seem reasonable?

Yeah, that could make certain things easier to unit test.</comment_new>


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 3 commits January 13, 2026 21:12
Co-authored-by: danielchalmers <7112040+danielchalmers@users.noreply.github.com>
Co-authored-by: danielchalmers <7112040+danielchalmers@users.noreply.github.com>
Co-authored-by: danielchalmers <7112040+danielchalmers@users.noreply.github.com>
Copilot AI changed the title [WIP] Make DebounceDispatcher and ThrottleDispatcher time-provider-aware Add TimeProvider support to DebounceDispatcher and ThrottleDispatcher Jan 13, 2026
Copilot AI requested a review from danielchalmers January 13, 2026 21:36
@danielchalmers danielchalmers marked this pull request as ready for review January 13, 2026 21:39
@mudbot mudbot bot added the enhancement Adds a new feature or enhances existing functionality (not fixing a defect) in the main library label Jan 13, 2026
@ScarletKuro
Copy link
Member

@copilot instead of adding optional TimeProvider? timeProvider = null to DebounceDispatcher and ThrottleDispatcher just make a new ctor overload, the default TimeProvider still should be the TimeProvider.System. This is more clean approach than optional arguments in ctor in this case.

Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 13, 2026

@copilot instead of adding optional TimeProvider? timeProvider = null to DebounceDispatcher and ThrottleDispatcher just make a new ctor overload, the default TimeProvider still should be the `Ti...

Changed to use separate constructor overloads. The original constructors remain unchanged, and new overloads accept TimeProvider as a non-optional parameter. This provides a cleaner API where the default behavior uses TimeProvider.System implicitly. Commit: cb7288c

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds TimeProvider support to DebounceDispatcher and ThrottleDispatcher to enable better testability using FakeTimeProvider instead of real time delays. This allows tests to advance time artificially rather than waiting for actual delays to elapse.

Changes:

  • Added TimeProvider parameter support to DebounceDispatcher and ThrottleDispatcher with constructor overloads
  • Updated components (MudTabs, MudDebouncedInput, MudColorPicker) to inject and pass TimeProvider
  • Converted select tests to use FakeTimeProvider for time-controlled testing

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/MudBlazor/Utilities/Throttle/ThrottleDispatcher.cs Added TimeProvider parameter with constructor overloads, migrated DateTime to DateTimeOffset, replaced DateTime.UtcNow with timeProvider.GetUtcNow()
src/MudBlazor/Utilities/Debounce/DebounceDispatcher.cs Added TimeProvider parameter with constructor overloads, migrated DateTime to DateTimeOffset, replaced Task.Delay with TimeProvider-aware delay
src/MudBlazor/Components/Tabs/MudTabs.razor.cs Injected TimeProvider via DI and passed to ThrottleDispatcher, changed field to nullable
src/MudBlazor/Components/Input/MudDebouncedInput.cs Injected TimeProvider via DI and passed to DebounceDispatcher
src/MudBlazor/Components/ColorPicker/MudColorPicker.razor.cs Injected TimeProvider via DI and passed to ThrottleDispatcher
src/MudBlazor.UnitTests/Utilities/Throttle/ThrottleDispatcherTests.cs Updated one test to use FakeTimeProvider for time-controlled testing
src/MudBlazor.UnitTests/Utilities/Debounce/DebounceDispatcherTests.cs Updated two tests to use FakeTimeProvider for time-controlled testing

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

private IResizeObserver? _resizeObserver;
private MudDropContainer<MudTabPanel>? _dropContainer;
private readonly ThrottleDispatcher _throttleDispatcher;
private ThrottleDispatcher? _throttleDispatcher;
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _throttleDispatcher field is used in the MudTabs.razor markup without null-checking (lines 17 and 56 of the razor file), but it's declared as nullable and only initialized in OnInitialized(). This could cause a NullReferenceException if the component attempts to render before OnInitialized() is called. Consider either: (1) making the field non-nullable and initializing it with a default value, or (2) adding null-conditional operator usage in the razor markup.

Suggested change
private ThrottleDispatcher? _throttleDispatcher;
private ThrottleDispatcher _throttleDispatcher = new();

Copilot uses AI. Check for mistakes.
@ScarletKuro
Copy link
Member

@copilot the copilot code-review found this concern

The _throttleDispatcher field is used in the MudTabs.razor markup without null-checking (lines 17 and 56 of the razor file), but it's declared as nullable and only initialized in OnInitialized(). This could cause a NullReferenceException if the component attempts to render before OnInitialized() is called. Consider either: (1) making the field non-nullable and initializing it with a default value, or (2) adding null-conditional operator usage in the razor markup.

Wouldn't it be better to wrap ThrottleDispatcher in lazy for MudTabs? When it's accessed by the rezor markup the object is suppose to be initialized already with the TimeProvider property being injected. If my theory is correct, then in that case the _throttleDispatcher with lazy should move back to ctor.

Wraps ThrottleDispatcher in Lazy to allow initialization in constructor while accessing injected TimeProvider. This eliminates the nullable field and potential null reference issues in razor markup.

Co-authored-by: ScarletKuro <19953225+ScarletKuro@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 13, 2026

@copilot the copilot code-review found this concern

The _throttleDispatcher field is used in the MudTabs.razor markup without null-checking (lines 17 and 56 of the razor file), but it's declared ...

You're correct! Using Lazy<ThrottleDispatcher> is the right approach. The _throttleDispatcher is now wrapped in Lazy and initialized in the constructor. This allows it to access the injected TimeProvider when first used (by the razor markup), eliminating the nullable field and potential null reference issues. The dispatcher is only created when first accessed via .Value, and we check IsValueCreated before disposing. Commit: 3132ac0

@ScarletKuro ScarletKuro merged commit 08fe274 into dev Jan 13, 2026
8 checks passed
@ScarletKuro ScarletKuro deleted the copilot/make-debounce-throttle-time-provider-aware branch January 13, 2026 22:28
This was referenced Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Adds a new feature or enhances existing functionality (not fixing a defect) in the main library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make DebounceDispatcher/ThrottleDispatcher time-provider-aware

3 participants