From 73ec26a605b21a2b331f065a976d0704a7f8d428 Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Tue, 17 Jun 2025 18:19:35 -0500 Subject: [PATCH 01/20] README: Add guide for testing a PR locally (#11487) --- README.md | 4 ++++ TESTING.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 TESTING.md diff --git a/README.md b/README.md index efd05175c38c..d3a7b45b92d3 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,10 @@ For larger features or changes, feel free to chat with us [on Discord](https://d 📚 Check out our [contribution guidelines](/CONTRIBUTING.md) to get started and learn more about how the project works. +### Testing a PR Locally + +✅ If a PR fixes something you reported, [locally test a preview version](/TESTING.md) to ensure your app works as expected before the update is released. + ## 🚀 Getting Started We have ready-to-go templates at the [MudBlazor.Templates](https://github.com/mudblazor/Templates) repository, or follow the quick install guide to set things up manually: diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 000000000000..e49cb1bd7d5b --- /dev/null +++ b/TESTING.md @@ -0,0 +1,58 @@ +# Testing a PR Locally + +1. **Clone and check out the PR (replace `PR_NUMBER`):** + +```bash +git clone https://github.com/MudBlazor/MudBlazor.git +cd MudBlazor +git fetch origin pull/PR_NUMBER/head:pr-branch +git checkout pr-branch +``` + +2. **Pack MudBlazor with a custom version:** + +```bash +dotnet pack src/MudBlazor/MudBlazor.csproj -c Release -o ./LocalNuGet -p:Version=8.0.0-custom +``` + +3. **Add a `nuget.config` in your app folder:** + +```xml + + + + + + + +``` + +4. **Update your app's `.csproj`:** + +```xml + +``` + +5. **Restore and build:** + +```bash +dotnet restore +dotnet build +``` + +## 🔄 Undo the changes after testing + +Once you're done testing, switch back to the official release by: + +- Removing the local source from your `nuget.config` (or deleting the file entirely if not otherwise needed). +- Restoring the original version in your `.csproj`: + +```xml + +``` + +Then run: + +```bash +dotnet restore +``` From be67fd4b0223be2018f236449ccd3651b0eed97c Mon Sep 17 00:00:00 2001 From: Timothy Bosman Date: Fri, 20 Jun 2025 22:33:51 +0200 Subject: [PATCH 02/20] Docs: Fixed installation JS reference typo (#11527) --- .../ManualMarkdown/InstallScriptManualNet8Code.html | 2 +- .../ManualMarkdown/InstallScriptManualWasmStandaloneCode.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualNet8Code.html b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualNet8Code.html index 7ee7eed40368..dc7da2d980e1 100644 --- a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualNet8Code.html +++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualNet8Code.html @@ -1,7 +1,7 @@ 
-<script src="_content/MudBlazor/MudBlazor.min.css?v=@(MudBlazor.Metadata.Version)"></script>
+<script src="_content/MudBlazor/MudBlazor.min.js?v=@(MudBlazor.Metadata.Version)"></script>
 
\ No newline at end of file diff --git a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandaloneCode.html b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandaloneCode.html index a6ca822456bd..bb67cec5f794 100644 --- a/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandaloneCode.html +++ b/src/MudBlazor.Docs/Pages/Getting Started/Installation/ManualMarkdown/InstallScriptManualWasmStandaloneCode.html @@ -2,7 +2,7 @@
 <!-- Important: Increment the version parameter whenever you update MudBlazor to prevent caching issues -->
-<script src="_content/MudBlazor/MudBlazor.min.css?v=1"></script>
+<script src="_content/MudBlazor/MudBlazor.min.js?v=1"></script>
 
\ No newline at end of file From 5d37790d68f72588544ff2a66b20712114053158 Mon Sep 17 00:00:00 2001 From: digitaldirk <22691956+digitaldirk@users.noreply.github.com> Date: Sun, 22 Jun 2025 13:08:03 -0700 Subject: [PATCH 03/20] Fix logo for dark mode for CONTRIBUTING.md (#11488) --- CONTRIBUTING.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3e2ae3659a8..76ca3bf4468f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,12 @@ -# ![MudBlazor](content/MudBlazor-GitHub-NoBg.png) +

+ + + + MudBlazor + +

- -- [](#) - [Information and Guidelines for Contributors](#information-and-guidelines-for-contributors) - [Code of Conduct](#code-of-conduct) - [Minimal Prerequisites to Compile from Source](#minimal-prerequisites-to-compile-from-source) From 17e0e56ee7549cfcc4f70fade1e04e9f52c29869 Mon Sep 17 00:00:00 2001 From: Anu6is Date: Mon, 23 Jun 2025 10:23:43 -0400 Subject: [PATCH 04/20] MudDialog: Fix Inline Dialog Title Refresh (#11504) --- .../Dialog/InlineDialogTitleRefreshTest.razor | 45 +++++++++++++++++++ .../Components/DialogTests.cs | 37 +++++++++++++++ .../Components/Dialog/MudDialog.razor.cs | 4 ++ 3 files changed, 86 insertions(+) create mode 100644 src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/InlineDialogTitleRefreshTest.razor diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/InlineDialogTitleRefreshTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/InlineDialogTitleRefreshTest.razor new file mode 100644 index 000000000000..971330d7b547 --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/InlineDialogTitleRefreshTest.razor @@ -0,0 +1,45 @@ +
+ + Show Dialog + +
+ + + + + @DialogTitle + + + + + + + +@code { + public static string __description__ = "Dialog title should update along with Progress Bar content"; + + private readonly DialogOptions _dialogOptions = new() { FullWidth = true }; + private int DialogProgress { get; set; } = 0; + private bool DialogVisible { get; set; } = false; + private string DialogTitle { get; set; } = "Initial state"; + + private async Task OpenDialog() + { + DialogVisible = true; + + for (int i = 0; i <= 10; i++) + { + await Task.Delay(150); // Simulate some work + DialogProgress = i * 10; // Simulate progress update + DialogTitle = $"Progress: {DialogProgress}%"; + StateHasChanged(); + } + + // Keep dialog open for a bit to observe final title + await Task.Delay(2000); + + DialogVisible = false; + DialogProgress = 0; + DialogTitle = "Initial state"; + } +} diff --git a/src/MudBlazor.UnitTests/Components/DialogTests.cs b/src/MudBlazor.UnitTests/Components/DialogTests.cs index d76adfcd0a9b..70ea53ae7a0f 100644 --- a/src/MudBlazor.UnitTests/Components/DialogTests.cs +++ b/src/MudBlazor.UnitTests/Components/DialogTests.cs @@ -1341,6 +1341,43 @@ public async Task DuplicateDialogTest() await Task.Delay(1000); comp.FindComponents().Count.Should().Be(1); } + + [Test] + public async Task Dialog_TitleContent_And_ProgressBar_ShouldUpdate() + { + var comp = Context.RenderComponent(); + comp.Markup.Trim().Should().BeEmpty(); + + var service = Context.Services.GetRequiredService(); + + var comp1 = Context.RenderComponent(); + + comp1.Find("button").Click(); + + var dialog = comp.Find("div.mud-dialog"); + + dialog.Should().NotBeNull(); + dialog.TextContent.Trim().Should().Be("Initial state"); // Initial title + comp.FindComponent().Instance.Value.Should().Be(0); // Initial progress value - 0 + + // Wait for mid-progress (simulate that loop is progressing) + await Task.Delay(1000); + //comp.Render(); + + dialog = comp.Find("div.mud-dialog"); + dialog.TextContent.Should().Contain("Progress"); //change from "Initial state" to "Progress" + + var progressComponent = comp.FindComponent(); + var progressValue = progressComponent.Instance.Value; + progressValue.Should().BeGreaterThan(0).And.BeLessThan(100); + + // Wait for dialog to close and state reset + await Task.Delay(3000); // Ensure loop finishes + comp.Render(); + + // Assert the dialog is now hidden + comp.Markup.Trim().Should().NotContain("mud-dialog"); + } } internal class CustomDialogService : DialogService diff --git a/src/MudBlazor/Components/Dialog/MudDialog.razor.cs b/src/MudBlazor/Components/Dialog/MudDialog.razor.cs index 683bd7b335a4..7eb16b262b1a 100644 --- a/src/MudBlazor/Components/Dialog/MudDialog.razor.cs +++ b/src/MudBlazor/Components/Dialog/MudDialog.razor.cs @@ -275,6 +275,10 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { // Forward render update to instance (_reference.Dialog as IMudStateHasChanged)?.StateHasChanged(); + + //forward render update to instance container + if (_reference.Dialog is MudDialog { DialogInstance: not null } dialog) + await InvokeAsync(dialog.DialogInstance!.StateHasChanged); } else { From 6027cf964268548e05ba61aa2edfd3ce2d03937b Mon Sep 17 00:00:00 2001 From: Anu6is Date: Mon, 23 Jun 2025 10:30:55 -0400 Subject: [PATCH 05/20] MudDialog: Fix CloseOnEscapeKey Not Triggered After Backdrop Click (#11475) --- .../Dialog/DialogCloseOnEscapeTest.razor | 45 ++++++++++ .../Components/DialogTests.cs | 86 ++++++++++++++++++- .../Dialog/MudDialogContainer.razor | 4 +- .../Dialog/MudDialogContainer.razor.cs | 26 +++++- src/MudBlazor/Styles/components/_dialog.scss | 1 + 5 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/DialogCloseOnEscapeTest.razor diff --git a/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/DialogCloseOnEscapeTest.razor b/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/DialogCloseOnEscapeTest.razor new file mode 100644 index 000000000000..e2234160cf6b --- /dev/null +++ b/src/MudBlazor.UnitTests.Viewer/TestComponents/Dialog/DialogCloseOnEscapeTest.razor @@ -0,0 +1,45 @@ +@inject IDialogService DialogService +@inject ISnackbar Snackbar + +Open Dialog (Test Esc Key) + +@code { + public static string __description__ = "Open Dialog -> Click overlay -> Press Esc"; + + private async Task OpenDialog() + { + var options = new DialogOptions + { + CloseOnEscapeKey = true, + BackdropClick = true, + }; + + // Define a RenderFragment for the dialog content directly + RenderFragment dialogContent = builder => + { + builder.OpenComponent(0); + builder.AddAttribute(1, "ChildContent", (RenderFragment)(b => b.AddContent(2, "Click the overlay (I shouldn't close), then press Esc. Or click inside me, then press Esc."))); + builder.CloseComponent(); + // Add a button to ensure focus can shift to an interactive element within the dialog + builder.OpenComponent(3); + builder.AddAttribute(4, "Variant", Variant.Filled); + builder.AddAttribute(5, "Class", "ma-4"); + builder.AddAttribute(6, "OnClick", EventCallback.Factory.Create(this, () => Snackbar.Add("Button clicked", Severity.Info))); + builder.AddAttribute(7, "ChildContent", (RenderFragment)(b => b.AddContent(8, "Test Button"))); + builder.CloseComponent(); + }; + + var parameters = new DialogParameters + { + [nameof(MudDialog.DialogContent)] = dialogContent, // Pass the RenderFragment + [nameof(MudDialog.OnBackdropClick)] = new EventCallback(null, (MouseEventArgs args) => + { + Snackbar.Add("Overlay clicked! Dialog should still be open. Now try pressing Escape.", Severity.Info); + // This handler intentionally does not close the dialog, to test refocus. + }) + }; + + // Use the built-in MudDialog and pass content and handlers through parameters + var dialog = await DialogService.ShowAsync("Test Escape Key", parameters, options); + } +} diff --git a/src/MudBlazor.UnitTests/Components/DialogTests.cs b/src/MudBlazor.UnitTests/Components/DialogTests.cs index 70ea53ae7a0f..79f3129882e8 100644 --- a/src/MudBlazor.UnitTests/Components/DialogTests.cs +++ b/src/MudBlazor.UnitTests/Components/DialogTests.cs @@ -1,8 +1,11 @@ using Bunit; using FluentAssertions; +using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.Extensions.DependencyInjection; -using MudBlazor.UnitTests.TestComponents; +using Microsoft.JSInterop; +using Microsoft.JSInterop.Infrastructure; +using Moq; using MudBlazor.UnitTests.TestComponents.Dialog; using NUnit.Framework; @@ -1378,8 +1381,87 @@ public async Task Dialog_TitleContent_And_ProgressBar_ShouldUpdate() // Assert the dialog is now hidden comp.Markup.Trim().Should().NotContain("mud-dialog"); } - } + [Test] + public async Task HandleBackgroundClickAsync_Should_Call_RefocusDialogAsync_When_BackdropClick_Disabled() + { + // Arrange + IDialogReference dialogReference = null; + + var jsRuntimeMock = new Mock(); + + jsRuntimeMock.Setup(x => x.InvokeAsync("Blazor._internal.domWrapper.focus", It.IsAny())); + + Context.Services.AddSingleton(jsRuntimeMock.Object); + + var comp = Context.RenderComponent(); + var service = Context.Services.GetRequiredService(); + var dialogOptions = new DialogOptions { BackdropClick = false, CloseOnEscapeKey = true }; + + //Act + await comp.InvokeAsync(async () => dialogReference = await service.ShowAsync("Test Dialog True", dialogOptions)); + + jsRuntimeMock.Verify(x => x.InvokeAsync("Blazor._internal.domWrapper.focus", + It.Is(args => + args.Length == 2 && + args[0] is ElementReference && + args[1] is bool)), + Times.AtMost(1)); // Focus should be called once when dialog is opened (FocusTrap) + + comp.Find("div.mud-dialog-container").Should().NotBeNull(); // Dialog is open + comp.Find("div.mud-overlay").Click(); // Simulate a click on the backdrop + + //Assert + comp.Find("div.mud-dialog-container").Should().NotBeNull(); // Dialog should still be open + + jsRuntimeMock.Verify(x => x.InvokeAsync("Blazor._internal.domWrapper.focus", + It.Is(args => + args.Length == 2 && + args[0] is ElementReference && + args[1] is bool)), + Times.AtMost(2)); // Focus should be called once when dialog is opened and once when backdrop is clicked (refouus) + } + + [Test] + public async Task HandleBackgroundClickAsync_Should_Call_OnBackdropClick_And_RefocusDialog_When_Delegate_Defined() + { + // Arrange + var jsRuntimeMock = new Mock(); + + jsRuntimeMock.Setup(x => x.InvokeAsync("Blazor._internal.domWrapper.focus", It.IsAny())); + + Context.Services.AddSingleton(jsRuntimeMock.Object); + + var comp = Context.RenderComponent(); + + // Create a dialog component with OnBackdropClick delegate + var dialogComponent = Context.RenderComponent(); + + // Act + await dialogComponent.FindComponent().Find("button").ClickAsync(new MouseEventArgs()); // Open the dialog + + jsRuntimeMock.Verify(x => x.InvokeAsync("Blazor._internal.domWrapper.focus", + It.Is(args => + args.Length == 2 && + args[0] is ElementReference && + args[1] is bool)), + Times.AtMost(1)); // Focus should be called once when dialog is opened + + comp.Find("div.mud-dialog-container").Should().NotBeNull(); // Dialog is open + + await comp.Find("div.mud-overlay").ClickAsync(new MouseEventArgs()); // Simulate a click on the backdrop + + // Assert + comp.Find("div.mud-dialog-container").Should().NotBeNull(); // Dialog should still be open (not closed) + + jsRuntimeMock.Verify(x => x.InvokeAsync("Blazor._internal.domWrapper.focus", + It.Is(args => + args.Length == 2 && + args[0] is ElementReference && + args[1] is bool)), + Times.AtMost(2)); // Focus should be called once when dialog opens and once for refocus after backdrop click + } + } internal class CustomDialogService : DialogService { public override IDialogReference CreateReference() => new CustomDialogReference(Guid.NewGuid(), this); diff --git a/src/MudBlazor/Components/Dialog/MudDialogContainer.razor b/src/MudBlazor/Components/Dialog/MudDialogContainer.razor index 6ee05cd0298c..eea75629412a 100644 --- a/src/MudBlazor/Components/Dialog/MudDialogContainer.razor +++ b/src/MudBlazor/Components/Dialog/MudDialogContainer.razor @@ -8,8 +8,8 @@ }
- -