From 3b5ef58bbefd175222fbaed490247876330b713c Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 8 Feb 2025 21:57:41 +0800 Subject: [PATCH] Add test --- .../Controls/AspireMenuButton.razor | 2 +- .../Controls/AspireMenuButton.razor.cs | 3 +- .../Controls/ClearSignalsButton.razor.cs | 2 + .../Components/Pages/ConsoleLogs.razor.cs | 10 ++- src/Aspire.Dashboard/Model/MenuButtonItem.cs | 1 + .../Pages/ConsoleLogsTests.cs | 62 ++++++++++++++++++- .../Shared/TestTimeProvider.cs | 5 +- 7 files changed, 77 insertions(+), 8 deletions(-) diff --git a/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor b/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor index 26d80cd94c..7c4881dda7 100644 --- a/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor +++ b/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor @@ -39,7 +39,7 @@ { "title", item.Tooltip ?? string.Empty } }; - + @item.Text @if (item.Icon != null) { diff --git a/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor.cs b/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor.cs index 52e38fac71..3463d32d5a 100644 --- a/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor.cs +++ b/src/Aspire.Dashboard/Components/Controls/AspireMenuButton.razor.cs @@ -40,7 +40,8 @@ public partial class AspireMenuButton : FluentComponentBase [Parameter] public string? Title { get; set; } - public string MenuButtonId { get; } = Identifier.NewId(); + [Parameter] + public string MenuButtonId { get; set; } = Identifier.NewId(); protected override void OnParametersSet() { diff --git a/src/Aspire.Dashboard/Components/Controls/ClearSignalsButton.razor.cs b/src/Aspire.Dashboard/Components/Controls/ClearSignalsButton.razor.cs index ddce0fb5aa..9c1329cd0c 100644 --- a/src/Aspire.Dashboard/Components/Controls/ClearSignalsButton.razor.cs +++ b/src/Aspire.Dashboard/Components/Controls/ClearSignalsButton.razor.cs @@ -35,6 +35,7 @@ protected override void OnParametersSet() _clearMenuItems.Add(new() { + Id = "clear-menu-all", Icon = s_clearAllResourcesIcon, OnClick = () => HandleClearSignal(null), Text = ControlsStringsLoc[name: nameof(ControlsStrings.ClearAllResources)], @@ -42,6 +43,7 @@ protected override void OnParametersSet() _clearMenuItems.Add(new() { + Id = "clear-menu-resource", Icon = s_clearSelectedResourceIcon, OnClick = () => HandleClearSignal(SelectedResource.Id?.GetApplicationKey()), IsDisabled = SelectedResource.Id == null, diff --git a/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs b/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs index 57592d1e7c..ae89ba7479 100644 --- a/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs @@ -71,6 +71,9 @@ private sealed class ConsoleLogsSubscription [Inject] public required DashboardCommandExecutor DashboardCommandExecutor { get; init; } + [Inject] + public required BrowserTimeProvider TimeProvider { get; init; } + [CascadingParameter] public required ViewportInformation ViewportInformation { get; init; } @@ -556,22 +559,23 @@ private async Task DownloadLogsAsync() using var streamReference = new DotNetStreamReference(stream); var safeDisplayName = string.Join("_", PageViewModel.SelectedResource!.DisplayName.Split(Path.GetInvalidFileNameChars())); - var fileName = $"{safeDisplayName}-{DateTime.Now.ToString("yyyyMMddhhmmss", CultureInfo.InvariantCulture)}.txt"; + var fileName = $"{safeDisplayName}-{TimeProvider.GetLocalNow().ToString("yyyyMMddhhmmss", CultureInfo.InvariantCulture)}.txt"; await JS.InvokeVoidAsync("downloadStreamAsFile", fileName, streamReference); } private async Task ClearConsoleLogs(ApplicationKey? key) { + var now = TimeProvider.GetUtcNow().UtcDateTime; if (key is null) { - _consoleLogFilters.FilterAllLogsDate = DateTime.UtcNow; + _consoleLogFilters.FilterAllLogsDate = now; _consoleLogFilters.FilterResourceLogsDates?.Clear(); } else { _consoleLogFilters.FilterResourceLogsDates ??= []; - _consoleLogFilters.FilterResourceLogsDates[key.Value.ToString()] = DateTime.UtcNow; + _consoleLogFilters.FilterResourceLogsDates[key.Value.ToString()] = now; } // Save filters to session storage so they're persisted when navigating to and from the console logs page. diff --git a/src/Aspire.Dashboard/Model/MenuButtonItem.cs b/src/Aspire.Dashboard/Model/MenuButtonItem.cs index 72f86c1499..a4548cbe45 100644 --- a/src/Aspire.Dashboard/Model/MenuButtonItem.cs +++ b/src/Aspire.Dashboard/Model/MenuButtonItem.cs @@ -13,5 +13,6 @@ public class MenuButtonItem public Icon? Icon { get; set; } public Func? OnClick { get; set; } public bool IsDisabled { get; set; } + public string? Id { get; set; } public IReadOnlyDictionary? AdditionalAttributes { get; set; } } diff --git a/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs b/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs index 8e3de576f0..67e243ce37 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs @@ -135,7 +135,62 @@ public async Task ResourceName_ViaUrlAndResourceLoaded_LogViewerUpdated() cut.WaitForState(() => instance._logEntries.EntriesCount > 0); } - private void SetupConsoleLogsServices(TestDashboardClient? dashboardClient = null) + [Fact] + public void ClearLogEntries_AllResources_LogsFilteredOut() + { + // Arrange + var consoleLogsChannel = Channel.CreateUnbounded>(); + var resourceChannel = Channel.CreateUnbounded>(); + var testResource = ModelTestHelpers.CreateResource(appName: "test-resource", state: KnownResourceState.Running); + var dashboardClient = new TestDashboardClient( + isEnabled: true, + consoleLogsChannelProvider: name => consoleLogsChannel, + resourceChannelProvider: () => resourceChannel, + initialResources: [ testResource ]); + var timeProvider = new TestTimeProvider(); + + SetupConsoleLogsServices(dashboardClient, timeProvider: timeProvider); + + var dimensionManager = Services.GetRequiredService(); + var viewport = new ViewportInformation(IsDesktop: true, IsUltraLowHeight: false, IsUltraLowWidth: false); + dimensionManager.InvokeOnViewportInformationChanged(viewport); + + // Act + var cut = RenderComponent(builder => + { + builder.Add(p => p.ResourceName, "test-resource"); + builder.Add(p => p.ViewportInformation, viewport); + }); + + var instance = cut.Instance; + var logger = Services.GetRequiredService>(); + var loc = Services.GetRequiredService>(); + + // Assert + logger.LogInformation("Waiting for selected resource."); + cut.WaitForState(() => instance.PageViewModel.SelectedResource == testResource); + cut.WaitForState(() => instance.PageViewModel.Status == loc[nameof(Resources.ConsoleLogs.ConsoleLogsWatchingLogs)]); + + logger.LogInformation("Log results are added to log viewer."); + consoleLogsChannel.Writer.TryWrite([new ResourceLogLine(1, "2025-02-08T10:16:08Z Hello world", IsErrorMessage: false)]); + cut.WaitForState(() => instance._logEntries.EntriesCount > 0); + + // Set current time to the date of the first entry so all entries are cleared. + var earliestEntry = instance._logEntries.GetEntries()[0]; + timeProvider.UtcNow = earliestEntry.Timestamp!.Value; + + logger.LogInformation("Clear current entries."); + cut.Find(".clear-button").Click(); + cut.Find("#clear-menu-all").Click(); + + cut.WaitForState(() => instance._logEntries.EntriesCount == 0); + + logger.LogInformation("New log results are added to log viewer."); + consoleLogsChannel.Writer.TryWrite([new ResourceLogLine(2, "2025-03-08T10:16:08Z Hello world", IsErrorMessage: false)]); + cut.WaitForState(() => instance._logEntries.EntriesCount > 0); + } + + private void SetupConsoleLogsServices(TestDashboardClient? dashboardClient = null, TestTimeProvider? timeProvider = null) { var version = typeof(FluentMain).Assembly.GetName().Version!; @@ -153,6 +208,9 @@ private void SetupConsoleLogsServices(TestDashboardClient? dashboardClient = nul var keycodeModule = JSInterop.SetupModule(GetFluentFile("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/KeyCode/FluentKeyCode.razor.js", version)); keycodeModule.Setup("RegisterKeyCode", _ => true); + JSInterop.SetupModule(GetFluentFile("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/Anchor/FluentAnchor.razor.js", version)); + JSInterop.SetupModule(GetFluentFile("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/AnchoredRegion/FluentAnchoredRegion.razor.js", version)); + JSInterop.SetupVoid("initializeContinuousScroll"); JSInterop.SetupVoid("resetContinuousScrollPosition"); @@ -160,7 +218,7 @@ private void SetupConsoleLogsServices(TestDashboardClient? dashboardClient = nul Services.AddLocalization(); Services.AddSingleton(loggerFactory); - Services.AddSingleton(); + Services.AddSingleton(timeProvider ?? new TestTimeProvider()); Services.AddSingleton(); Services.AddSingleton(); Services.AddSingleton>(Options.Create(new DashboardOptions())); diff --git a/tests/Aspire.Dashboard.Components.Tests/Shared/TestTimeProvider.cs b/tests/Aspire.Dashboard.Components.Tests/Shared/TestTimeProvider.cs index 8cfc284b3d..fc4d4c4df5 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Shared/TestTimeProvider.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Shared/TestTimeProvider.cs @@ -10,13 +10,16 @@ public sealed class TestTimeProvider : BrowserTimeProvider { private TimeZoneInfo? _localTimeZone; + public DateTimeOffset UtcNow { get; set; } + public TestTimeProvider() : base(NullLoggerFactory.Instance) { + UtcNow = new DateTimeOffset(2025, 12, 20, 23, 59, 59, TimeSpan.Zero); } public override DateTimeOffset GetUtcNow() { - return new DateTimeOffset(2025, 12, 20, 23, 59, 59, TimeSpan.Zero); + return UtcNow; } public override TimeZoneInfo LocalTimeZone => _localTimeZone ??= TimeZoneInfo.CreateCustomTimeZone(nameof(TestTimeProvider), TimeSpan.FromHours(1), nameof(TestTimeProvider), nameof(TestTimeProvider));