Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,11 @@
<SectionHeader Class="mt-6">
<Description>
<MudAlert Class="mt-4" Severity="Severity.Warning">
If you experience issues such as certain components not responding to user interactions, your render mode may be configured incorrectly.
Static rendering is not supported and the rendering mode must be interactive.
If you experience issues such as certain components not responding to user interactions, or a logged
<CodeInline>Missing &lt;MudPopoverProvider /&gt;</CodeInline> error, your render mode may be configured incorrectly.
Static rendering is not supported: these providers must render in the same interactive render mode as the components that use them.
With global interactivity (the render mode set on <CodeInline>&lt;Routes /&gt;</CodeInline> in <CodeInline>App.razor</CodeInline>) keep them here in <CodeInline>MainLayout.razor</CodeInline>.
With per-page interactivity, <CodeInline>MainLayout.razor</CodeInline> renders statically, so add the providers to each interactive page instead.
<MudLink Href="https://learn.microsoft.com/aspnet/core/blazor/components/render-modes" Target="_blank">Learn about render modes</MudLink>
or
<MudLink Href="https://github.com/MudBlazor/MudBlazor/discussions/7430" Target="_blank">view a related discussion</MudLink>.
Expand Down
38 changes: 29 additions & 9 deletions src/MudBlazor.UnitTests/Services/Popover/PopoverServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using AwesomeAssertions;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Time.Testing;
Expand Down Expand Up @@ -138,22 +139,41 @@ public async Task CreatePopoverAsync_CheckForPopoverProvider(bool checkForPopove
{
// Arrange
var jsRuntimeMock = Mock.Of<IJSRuntime>();
var loggerMock = new Mock<ILogger<PopoverService>>();
var popover = new PopoverMock();
var options = new PopoverOptions { CheckForPopoverProvider = checkForPopoverProvider };
var service = CreateService(jsRuntimeMock, options);
var service = new PopoverService(loggerMock.Object, jsRuntimeMock, new FakeTimeProvider(), new OptionsWrapper<PopoverOptions>(options));

// Act
// No MudPopoverProvider is subscribed (ObserversCount == 0).
// This must never throw: throwing from the popover's OnInitializedAsync tears down the circuit mid-render and surfaces as a cryptic ObjectDisposedException on the other components (#11887).
// With the check on, it logs an error instead.
var create = () => service.CreatePopoverAsync(popover);

// Assert
if (checkForPopoverProvider)
{
await create.Should().ThrowAsync<InvalidOperationException>();
}
else
{
await create.Should().NotThrowAsync<InvalidOperationException>();
}
await create.Should().NotThrowAsync();
service.ActivePopovers.Should().ContainSingle(x => x.Id == popover.Id);
loggerMock.VerifyLogging(PopoverService.MissingProviderMessage, LogLevel.Error, Times.Exactly(checkForPopoverProvider ? 1 : 0));
}

[Test]
public async Task CreatePopoverAsync_LogsMissingProviderOnlyOnce()
{
// Arrange
var jsRuntimeMock = Mock.Of<IJSRuntime>();
var loggerMock = new Mock<ILogger<PopoverService>>();
var options = new PopoverOptions { CheckForPopoverProvider = true };
var service = new PopoverService(loggerMock.Object, jsRuntimeMock, new FakeTimeProvider(), new OptionsWrapper<PopoverOptions>(options));

// Act
// Several popovers are created without a provider (e.g. multiple pickers on a page).
// The actionable error must be logged only once so the log is not flooded with the same message.
await service.CreatePopoverAsync(new PopoverMock());
await service.CreatePopoverAsync(new PopoverMock());

// Assert
service.ActivePopovers.Should().HaveCount(2);
loggerMock.VerifyLogging(PopoverService.MissingProviderMessage, LogLevel.Error, Times.Once());
}

[Test]
Expand Down
18 changes: 13 additions & 5 deletions src/MudBlazor/Services/Popover/PopoverService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,22 @@ namespace MudBlazor;
/// </remarks>
internal class PopoverService : IPopoverService, IBatchTimerHandler<MudPopoverHolder>
{
internal const string MissingProviderMessage =
"Missing <MudPopoverProvider /> in the active render scope, so popovers cannot be displayed. " +
"Add <MudPopoverProvider /> within the same interactive render mode as the components that use it: in your layout for global interactivity, or on each page for per-page interactivity. " +
"See https://mudblazor.com/getting-started/installation#manual-install-add-components";

private bool _disposed;
private bool _isInitializing;
private bool _missingProviderLogged;
private readonly PopoverJsInterop _popoverJsInterop;
private readonly CancellationToken _cancellationToken;
private readonly Dictionary<Guid, MudPopoverHolder> _holders;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly BatchPeriodicQueue<MudPopoverHolder> _batchExecutor;
private readonly ObserverManager<Guid, IPopoverObserver> _observerManager;
private readonly TimeProvider _timeProvider;
private readonly ILogger<PopoverService> _logger;

/// <inheritdoc />
public IEnumerable<IMudPopoverHolder> ActivePopovers => _holders.Values;
Expand Down Expand Up @@ -65,6 +72,7 @@ internal class PopoverService : IPopoverService, IBatchTimerHandler<MudPopoverHo
public PopoverService(ILogger<PopoverService> logger, IJSRuntime jsInterop, TimeProvider timeProvider, IOptions<PopoverOptions>? options = null)
{
_timeProvider = timeProvider;
_logger = logger;
PopoverOptions = options?.Value ?? new PopoverOptions();
_holders = new Dictionary<Guid, MudPopoverHolder>();
_cancellationTokenSource = new CancellationTokenSource();
Expand Down Expand Up @@ -107,12 +115,12 @@ public async Task CreatePopoverAsync(IPopover popover)
return;
}

if (PopoverOptions.CheckForPopoverProvider)
if (PopoverOptions.CheckForPopoverProvider && ObserversCount == 0 && !_missingProviderLogged)
{
if (ObserversCount == 0)
{
throw new InvalidOperationException($"Missing <{nameof(MudPopoverProvider)} />, please add it to your layout. See https://mudblazor.com/getting-started/installation#manual-install-add-components");
}
// No MudPopoverProvider is subscribed in this render scope.
// Throwing here (this runs from the popover's OnInitializedAsync) tears down the circuit mid-render and surfaces as a cryptic ObjectDisposedException on sibling components (#11887), so log once and continue; the holder is harmless without a provider.
_missingProviderLogged = true;
_logger.LogError(MissingProviderMessage);
}

var holder = new MudPopoverHolder(popover.Id, _timeProvider)
Expand Down
Loading