Skip to content

Components: Migrate time-dependent logic to TimeProvider abstraction#12592

Merged
danielchalmers merged 25 commits intodevfrom
copilot/migrate-components-to-timeprovider
Feb 2, 2026
Merged

Components: Migrate time-dependent logic to TimeProvider abstraction#12592
danielchalmers merged 25 commits intodevfrom
copilot/migrate-components-to-timeprovider

Conversation

Copy link
Contributor

Copilot AI commented Feb 1, 2026

  • Analyze codebase to identify banned APIs
  • Add TimeProviderExtensions helper for tests
  • Migrate Snackbar system (Snackbar.cs, SnackBarMessageState.cs) - Replace Stopwatch and System.Threading.Timer with TimeProvider
  • Migrate MudPopoverHolder - Replace DateTime.Now with TimeProvider
  • Migrate MudMenu - Replace DateTime fields with DateTimeOffset and inject TimeProvider
  • Migrate MudSelect - Replace DateTime fields with DateTimeOffset and inject TimeProvider
  • Migrate MudDatePicker - Replace DateTime.UtcNow with TimeProvider
  • Migrate MudTimePicker - Replace Task.Delay with TimeProvider overload
  • Migrate MudDateRangePicker - Replace Task.Delay with TimeProvider overload
  • Migrate MudBaseDatePicker - Replace Task.Delay with TimeProvider overload
  • Migrate TimeSeries chart - Replace DateTime.Now with TimeProvider
  • Migrate MudAutocomplete - Replace System.Threading.Timer with ITimer
  • Migrate MudForm - Replace System.Threading.Timer with ITimer
  • Migrate MudCarousel - Replace System.Threading.Timer with ITimer
  • Update PopoverServiceTests for new TimeProvider parameter
  • Update SnackbarServiceTests for new TimeProvider parameter
  • Code review and verify all banned APIs removed
  • Fix TimeProvider registration for AddMudBlazorSnackbar() and AddMudPopoverService()
Original prompt

This section details on the original issue you should resolve

<issue_title>Migrate Components to TimeProvider</issue_title>
<issue_description>This issue tracks the migration of time-dependent components to the TimeProvider abstraction (introduced in .NET 8). This change allows the use of FakeTimeProvider in unit tests, eliminating flakiness caused by Task.Delay, Stopwatch, and system clock drift. We will remove [NonParallelizable] from tests for any components modified in this issue and ensure the entire suite remains correct when run in parallel.

Problem Statement

Key components currently rely on DateTime.Now, Stopwatch, System.Threading.Timer, or wall-clock Task.Delay.

  • Tests are slow: They rely on Task.Delay() to simulate debouncing.
  • Tests are flaky: CI environments vary in speed; a 500ms delay might finish before the CPU processes the task, causing assertion failures.
  • Lack of Determinism: We cannot currently test "infinite" snackbars or long-running timers without actually waiting.

Implementation Pattern

1. Dependency Injection Rules

Internal services (C# services registered in DI):

  • Use constructor injection for TimeProvider (strict DI).
  • Do not add fallback constructors.

Razor components (e.g., MudAutocomplete, MudSelect, etc.):

  • Keep the existing MudBlazor component pattern: use property injection via [Inject].
  • Maintain the standard null! initialization pattern to match existing architecture.

Example for components:

[Inject] private TimeProvider TimeProvider { get; set; } = null!;

Service Registration (Library Citizenship):
Update AddMudServices to ensure a provider exists if the host hasn’t registered one:

services.TryAddSingleton(TimeProvider.System);

2. Type Migration Rules

  • Now: Replace all direct “now” reads with TimeProvider.GetUtcNow() (for components/services using property injection, use the injected TimeProvider instance).

  • Internal State: Use DateTimeOffset for all internal timestamps (e.g., _lastClick, _debounceStart, _activationDate).

  • Timers: Replace System.Threading.Timer with ITimer via TimeProvider.CreateTimer().

    • Timer lifecycle: Always dispose/replace prior timers on reschedule and guard callbacks against stale state where applicable (e.g., debounce).
  • Stopwatch: Remove Stopwatch usage entirely.

    • Prefer ITimer + GetUtcNow() deadline/remaining-time models for UI timeouts.
    • Use TimeProvider.GetTimestamp() only when monotonic high-precision timing is required.

Targets

MudAutocomplete

  • Current State: Uses System.Threading.Timer for debouncing search input (and may use TimeProvider partially).
  • Change: Replace with ITimer created via TimeProvider.
  • Benefit: Tests can trigger debounce callbacks deterministically via fakeTime.Advance().

MudMenu

  • Change: Inject TimeProvider via [Inject] (if not already). Change _lastKeyboardActivation type from DateTime to DateTimeOffset.
  • Benefit: Stabilizes the 50ms guard for synthetic clicks.

MudSelect

  • Change: Inject TimeProvider via [Inject] (if not already). Change _lastSearchTime type from DateTime to DateTimeOffset.
  • Benefit: Precise control over the text-search buffer reset.

Snackbar

  • Current State: Uses Stopwatch to handle pausing (on hover) and resuming.
  • Change: Replace Stopwatch with a deadline + remaining-time model using DateTimeOffset from TimeProvider.GetUtcNow() and an ITimer created via TimeProvider.CreateTimer().
  • Implementation Detail: When the snackbar starts, compute deadline = now + timeout and create a one-shot timer for timeout. On hover/pause, dispose the timer and compute remaining = max(deadline - now, 0). On resume, recreate the one-shot timer for remaining and recompute deadline = now + remaining. For “infinite” snackbars, keep remaining/deadline unset and do not create a timer.

General Render Cycle

  • Change: Replace await Task.Delay(1) with a TimeProvider overload (no wall-clock delays), e.g.:

    await Task.Delay(TimeSpan.FromMilliseconds(1), TimeProvider);
  • Benefit: Allows tests to deterministically skip “wait for render” hacks.

Additional Known Call Sites (Explicitly In Scope)

Include the following in the migration work (they currently use banned APIs and should be updated per the rules above):

  • Charts: MudTimeSeriesChart (e.g., default date ranges based on DateTime.Now)
  • Pickers: MudDatePicker / MudDateRangePicker (e.g., DateTime.UtcNow debouncing guards and DateTime.Now default ranges)
  • Popovers: MudPopoverHolder (e.g., tracking activation time via DateTime.Now)

Usage Audit Requirement

Some components already have TimeProvider injected but still use banned APIs in other parts of the same fil...


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Migrate time-dependent components to TimeProvider Migrate time-dependent components to TimeProvider abstraction Feb 1, 2026
Copilot AI requested a review from danielchalmers February 1, 2026 03:31
@danielchalmers

This comment was marked as outdated.

@danielchalmers

This comment was marked as outdated.

This comment was marked as outdated.

@danielchalmers danielchalmers marked this pull request as ready for review February 1, 2026 04:39
@mudbot mudbot bot added the refactor Reorganizes code and has no changes to the API or functionality in the main library label Feb 1, 2026
Summary:
- Registered `FakeTimeProvider` for all test contexts in TestContextExtensions.cs.
- Updated time-based component tests to use `Context.AdvanceTime`, removed `[NonParallelizable]`, and added render waits where needed in AutocompleteTests.cs, MenuTests.cs, SelectTests.cs, SnackbarTests.cs, CarouselTests.cs, FormTests.cs, DatePickerTests.cs, and DateRangePickerTests.cs.
- Replaced `TimeProvider.System` usage with `FakeTimeProvider` in PopoverServiceTests.cs and SnackbarServiceTests.cs
@danielchalmers danielchalmers added breaking change This change will require consumer code updates and removed refactor Reorganizes code and has no changes to the API or functionality in the main library labels Feb 1, 2026
@mudbot mudbot bot changed the title Migrate time-dependent components to TimeProvider abstraction Components: Migrate time-dependent logic to TimeProvider abstraction Feb 1, 2026
This was referenced Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change This change will require consumer code updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate Components to TimeProvider

3 participants