diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml index d1570ff922..166b0f6235 100644 --- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml +++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml @@ -2001,7 +2001,8 @@ - Gets or sets a value indicating whether the grid is in a loading data state. + Gets or sets a value to indicate the grid loading data state. + If not set and a is present, the grid will show until the provider's first return. @@ -4405,6 +4406,8 @@ This method requires dynamic access to code. This code may be removed by the trimmer. + If the assembly is not yet loaded, it will be loaded by the method `Assembly.Load`. + To avoid any issues, the assembly must be loaded before calling this method (e.g. adding an emoji in your code). Raised when the is not found in predefined emojis. @@ -4422,6 +4425,9 @@ + + + FluentUI Emoji meta-data. @@ -4992,6 +4998,8 @@ The to instantiate. This method requires dynamic access to code. This code may be removed by the trimmer. + If the assembly is not yet loaded, it will be loaded by the method `Assembly.Load`. + To avoid any issues, the assembly must be loaded before calling this method (e.g. adding an icon in your code). Raised when the is not found in predefined icons. @@ -5009,6 +5017,9 @@ + + + @@ -5232,6 +5243,11 @@ Gets the content type of the current file in an upload process. + + + Gets the last modified date of the current file in an upload process. + + Gets the label to display in an upload process. diff --git a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridRemoteData.razor b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridRemoteData.razor index 8f49828f2c..00804f73af 100644 --- a/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridRemoteData.razor +++ b/examples/Demo/Shared/Pages/DataGrid/Examples/DataGridRemoteData.razor @@ -5,7 +5,6 @@
} - @if (Loading) + @if (EffectiveLoadingValue) { @_renderLoadingContent } diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs index f6d34a9443..9036b69197 100644 --- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs @@ -239,10 +239,11 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve public RenderFragment? EmptyContent { get; set; } /// - /// Gets or sets a value indicating whether the grid is in a loading data state. + /// Gets or sets a value to indicate the grid loading data state. + /// If not set and a is present, the grid will show until the provider's first return. /// [Parameter] - public bool Loading { get; set; } + public bool? Loading { get; set; } /// /// Gets or sets the content to render when is true. @@ -287,7 +288,11 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve /// Gets or sets a value indicating whether the grids' first cell should be focused. /// [Parameter] - public bool AutoFocus{ get; set; } = false; + public bool AutoFocus { get; set; } = false; + + // Returns Loading if set (controlled). If not controlled, + // we assume the grid is loading until the next data load completes + internal bool EffectiveLoadingValue => Loading ?? ItemsProvider is not null; private ElementReference? _gridReference; private Virtualize<(int, TGridItem)>? _virtualizeComponent; @@ -403,7 +408,7 @@ protected override Task OnParametersSetAsync() Pagination?.ItemsPerPage != _lastRefreshedPaginationState?.ItemsPerPage || Pagination?.CurrentPageIndex != _lastRefreshedPaginationState?.CurrentPageIndex; - var mustRefreshData = dataSourceHasChanged || paginationStateHasChanged; + var mustRefreshData = dataSourceHasChanged || paginationStateHasChanged || Loading is null; // We don't want to trigger the first data load until we've collected the initial set of columns, // because they might perform some action like setting the default sort order, so it would be wasteful @@ -643,7 +648,7 @@ public Task ShowColumnResizeAsync(int index) return (index >= 0 && index < _columns.Count) ? ShowColumnResizeAsync(_columns[index]) : Task.CompletedTask; } - public void SetLoadingState(bool loading) + public void SetLoadingState(bool? loading) { Loading = loading; } @@ -733,9 +738,10 @@ private async Task RefreshDataCoreAsync() _internalGridContext.TotalViewItemCount = Pagination?.ItemsPerPage ?? providerResult.TotalItemCount; Pagination?.SetTotalItemCountAsync(_internalGridContext.TotalItemCount); - if (_internalGridContext.TotalItemCount > 0) + if (_internalGridContext.TotalItemCount > 0 && Loading is null) { Loading = false; + StateHasChanged(); } // We're supplying the row _index along with each row's data because we need it for aria-rowindex, and we have to account for @@ -757,9 +763,10 @@ private async ValueTask> ResolveItemsRequestA if (ItemsProvider is not null) { var gipr = await ItemsProvider(request); - if (gipr.Items is not null) + if (gipr.Items is not null && Loading is null) { Loading = false; + StateHasChanged(); } return gipr; } @@ -791,7 +798,7 @@ private string AriaSortValue(ColumnBase column) private string? StyleValue => new StyleBuilder(Style) .AddStyle("grid-template-columns", _internalGridTemplateColumns, !string.IsNullOrWhiteSpace(_internalGridTemplateColumns)) .AddStyle("grid-template-rows", "auto 1fr", _internalGridContext.Items.Count == 0 || Items is null) - .AddStyle("height", $"calc(100% - {(int)RowSize}px)", _internalGridContext.TotalItemCount == 0 || Loading) + .AddStyle("height", $"calc(100% - {(int)RowSize}px)", _internalGridContext.TotalItemCount == 0 || EffectiveLoadingValue) .Build(); private string? ColumnHeaderClass(ColumnBase column) diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs index 327a9339c0..fc7894c29a 100644 --- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs +++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs @@ -68,15 +68,15 @@ public partial class FluentDataGridCell : FluentComponentBase .Build(); protected string? StyleValue => new StyleBuilder(Style) - .AddStyle("grid-column", GridColumn.ToString(), () => (!Grid.Loading && Grid.Items is not null) || Grid.Virtualize) + .AddStyle("grid-column", GridColumn.ToString(), () => (!Grid.EffectiveLoadingValue && Grid.Items is not null) || Grid.Virtualize) .AddStyle("text-align", "center", Column is SelectColumn) .AddStyle("align-content", "center", Column is SelectColumn) .AddStyle("padding-inline-start", "calc(((var(--design-unit)* 3) + var(--focus-stroke-width) - var(--stroke-width))* 1px)", Column is SelectColumn && Owner.RowType == DataGridRowType.Default) .AddStyle("padding-top", "calc(var(--design-unit) * 2.5px)", Column is SelectColumn && (Grid.RowSize == DataGridRowSize.Medium || Owner.RowType == DataGridRowType.Header)) .AddStyle("padding-top", "calc(var(--design-unit) * 1.5px)", Column is SelectColumn && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default) - .AddStyle("height", $"{Grid.ItemSize:0}px", () => !Grid.Loading && Grid.Virtualize && Owner.RowType == DataGridRowType.Default) - .AddStyle("height", $"{(int)Grid.RowSize}px", () => !Grid.Loading && !Grid.Virtualize && Grid.Items is not null && !Grid.MultiLine) - .AddStyle("height", "100%", InternalGridContext.TotalItemCount == 0 || (Grid.Loading && Grid.Items is null) || Grid.MultiLine) + .AddStyle("height", $"{Grid.ItemSize:0}px", () => !Grid.EffectiveLoadingValue && Grid.Virtualize && Owner.RowType == DataGridRowType.Default) + .AddStyle("height", $"{(int)Grid.RowSize}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && Grid.Items is not null && !Grid.MultiLine) + .AddStyle("height", "100%", InternalGridContext.TotalItemCount == 0 || (Grid.EffectiveLoadingValue && Grid.Items is null) || Grid.MultiLine) .AddStyle("min-height", "44px", Owner.RowType != DataGridRowType.Default) .AddStyle(Owner.Style) .Build(); diff --git a/tests/Core/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html b/tests/Core/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html new file mode 100644 index 0000000000..15f37cb1c5 --- /dev/null +++ b/tests/Core/DataGrid/FluentDataGridTests.FluentDataGrid_Default.verified.razor.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + +
+
+
Name
+
+
Denis Voituron
Vincent Baaij
Bill Gates
\ No newline at end of file diff --git a/tests/Core/DataGrid/FluentDataGridTests.razor b/tests/Core/DataGrid/FluentDataGridTests.razor new file mode 100644 index 0000000000..849667fe06 --- /dev/null +++ b/tests/Core/DataGrid/FluentDataGridTests.razor @@ -0,0 +1,156 @@ +@using FluentAssertions +@using Xunit +@inherits TestContext + +@code { + public FluentDataGridTests() + { + var dataGridModule = JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); + dataGridModule.SetupModule("init", _ => true); + + // Register services + Services.AddSingleton(LibraryConfiguration.ForUnitTests); + Services.AddScoped(factory => new KeyCodeService()); + } + + [Fact] + public void FluentDataGrid_Default() + { + // Arrange && Act + var cut = Render>( + @ + + + +

empty content

+
); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentDataGrid_With_Empty_Items_Stays_Loading_Until_Changed() + { + // Arrange && Act + var cut = Render>( + @ +

empty content

+

loading content

+ + + +
); + + // Assert + cut.Find("#loading-content").Should().NotBeNull(); + Assert.Throws(() => cut.Find("#empty-content")); + + cut.SetParametersAndRender(parameters => parameters + .Add(p => p.Loading, false)); + + Assert.Throws(() => cut.Find("#loading-content")); + cut.Find("#empty-content").Should().NotBeNull(); + } + + [Fact] + public async Task FluentDataGrid_With_ItemProvider_Stays_Loading_Until_ChangedAsync() + { + ValueTask> GetItems(GridItemsProviderRequest request) + { + return ValueTask.FromResult(GridItemsProviderResult.From( + Array.Empty(), + 0)); + } + + var cut = Render>( + @ +

empty content

+

loading content

+ + +

@context.Name

+
+
+
); + + // Assert + var dataGrid = cut.Instance; + cut.Find("#loading-content").Should().NotBeNull(); + + // should stay loading even after data refresh + await cut.InvokeAsync(() => dataGrid.RefreshDataAsync()); + cut.Find("#loading-content").Should().NotBeNull(); + + // now not loading but still with 0 items, should render empty content + cut.SetParametersAndRender(parameters => parameters + .Add(p => p.Loading, false)); + + cut.Find("#empty-content").Should().NotBeNull(); + } + + [Fact] + public async Task FluentDataGrid_With_ItemProvider_And_Uncontrolled_Loading_Starts_Loading() + { + var tcs = new TaskCompletionSource(); + async ValueTask> GetItems(GridItemsProviderRequest request) + { + await tcs.Task; + var numberOfItems = 1; + return GridItemsProviderResult.From( + GetCustomers().Take(numberOfItems).ToArray(), + numberOfItems); + } + + var cut = Render>( + @ +

empty content

+

loading content

+ + +

@context.Name

+
+
+
); + + // Assert + var dataGrid = cut.Instance; + + // Data is still loading, so loading content should be displayed + cut.Find("#loading-content").Should().NotBeNull(); + + tcs.SetResult(); + + // Data is no longer loading, so loading content should not be displayed after re-render + // wait for re-render here + cut.WaitForState(() => cut.Find("p").TextContent == GetCustomers().First().Name); + + Assert.Throws(() => cut.Find("#loading-content")); + + // should stay not loading even after data refresh + await cut.InvokeAsync(() => dataGrid.RefreshDataAsync()); + Assert.Throws(() => cut.Find("#loading-content")); + + // if we explicitly set Loading back to null, we should see the same behaviors because data should + // be refreshed + tcs = new TaskCompletionSource(); + cut.SetParametersAndRender(parameters => parameters + .Add(p => p.Loading, null)); + cut.Find("#loading-content").Should().NotBeNull(); + + tcs.SetResult(); + + cut.WaitForState(() => cut.Find("p").TextContent == GetCustomers().First().Name); + Assert.Throws(() => cut.Find("#loading-content")); + } + + // Sample data... + private IEnumerable GetCustomers() + { + yield return new Customer(1, "Denis Voituron"); + yield return new Customer(2, "Vincent Baaij"); + yield return new Customer(3, "Bill Gates"); + } + + private record Customer(int Id, string Name); +} diff --git a/tests/Core/_ToDo/DataGrid/FluentDataGridTests.cs b/tests/Core/_ToDo/DataGrid/FluentDataGridTests.cs deleted file mode 100644 index 1ee9ab1293..0000000000 --- a/tests/Core/_ToDo/DataGrid/FluentDataGridTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Bunit; -using Microsoft.JSInterop; -using Xunit; - -namespace Microsoft.FluentUI.AspNetCore.Components.Tests.DataGrid; -public class FluentDataGridTests : TestBase -{ - public FluentDataGridTests() - { - TestContext.JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/DataGrid/FluentDataGrid.razor.js"); - TestContext.JSInterop.Setup("init", _ => true); - } - - [Fact(Skip = "Need to figure out how to do this test")] - public void FluentDataGrid_Default() - { - //Arrange - //Services.AddSingleton(); - var childContent = "render me"; - var emptyContent = "render me"; - - bool virtualize = default!; - float itemSize = default!; - bool resizableColumns = default!; - PaginationState pagination = default!; - bool noTabbing = default!; - GenerateHeaderOption? generateHeader = default!; - string gridTemplateColumns = default!; - - var cut = TestContext.RenderComponent>(parameters => parameters - .Add(p => p.Items, GetCustomers().AsQueryable()) - - .AddChildContent(childContent) - .Add(p => p.Virtualize, virtualize) - .Add(p => p.ItemSize, itemSize) - .Add(p => p.ResizableColumns, resizableColumns) - .Add(p => p.Pagination, pagination) - .Add(p => p.NoTabbing, noTabbing) - .Add(p => p.GenerateHeader, generateHeader) - .Add(p => p.GridTemplateColumns, gridTemplateColumns) - - .Add(p => p.EmptyContent, emptyContent) - ); - //Act - - //Assert - cut.Verify(); - } - - // Sample data... - private IEnumerable GetCustomers() - { - yield return new Customer(1, "Denis Voituron"); - yield return new Customer(2, "Vincent Baaij"); - yield return new Customer(3, "Bill Gates"); - } - - private record Customer(int Id, string Name); -}