diff --git a/src/Web/Shared/Components/Form/DataTable.razor b/src/Web/Shared/Components/Form/DataTable.razor index e88dbee316..88440ecc38 100644 --- a/src/Web/Shared/Components/Form/DataTable.razor +++ b/src/Web/Shared/Components/Form/DataTable.razor @@ -130,6 +130,7 @@ private async Task GetPageAsync(int page) { + var restartFromFirstPage = false; this._isLoading = true; try { @@ -151,6 +152,10 @@ else { this._hasMoreEntries = false; + + // A refresh of a page which does not exist anymore (for example after a filter reduced the results) would + // show an empty table without a way to notice why. Start over from the first page instead. + restartFromFirstPage = isRefresh && page > 1; } } finally @@ -158,5 +163,10 @@ this._isLoading = false; await this.InvokeAsync(this.StateHasChanged).ConfigureAwait(false); } + + if (restartFromFirstPage) + { + await this.GetPageAsync(1); + } } } diff --git a/tests/MUnique.OpenMU.Web.Tests/Components/DataTableTests.cs b/tests/MUnique.OpenMU.Web.Tests/Components/DataTableTests.cs new file mode 100644 index 0000000000..37499f27c1 --- /dev/null +++ b/tests/MUnique.OpenMU.Web.Tests/Components/DataTableTests.cs @@ -0,0 +1,75 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Web.Tests.Components; + +using Bunit; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; +using MUnique.OpenMU.Web.Shared.Components.Form; +using MUnique.OpenMU.Web.Shared.Services; + +/// +/// Tests for the component. +/// +[TestFixture] +public class DataTableTests +{ + /// + /// A refresh of a page which doesn't exist anymore, e.g. after a filter reduced the results, + /// starts over from the first page instead of showing an empty table. + /// + [Test] + public void RefreshOfEmptyPage_StartsOverFromFirstPage() + { + using var context = new BunitContext(); + var dataService = new TestDataService(Enumerable.Range(1, 45).Select(i => new Item(i))); + context.Services.AddSingleton>(dataService); + var component = context.Render>(parameters => parameters + .Add(table => table.TableHeader, (RenderFragment)(_ => { })) + .Add(table => table.ItemTemplate, item => builder => builder.AddContent(0, item.Number))); + + // The buttons are: previous page, page number, next page. + component.FindAll("button")[2].Click(); + component.FindAll("button")[2].Click(); + component.WaitForAssertion(() => Assert.That(component.Instance.Page, Is.EqualTo(3))); + + dataService.SetItems(Enumerable.Range(1, 3).Select(i => new Item(i))); + + component.WaitForAssertion(() => + { + Assert.That(component.Instance.Page, Is.EqualTo(1)); + Assert.That(component.FindAll("tbody tr"), Has.Count.EqualTo(3)); + }); + } + + /// + /// An item of the table. + /// + /// The number of the item. + public sealed record Item(int Number); + + private sealed class TestDataService : IDataService, ISupportDataChangedNotification + { + private List _items; + + public TestDataService(IEnumerable items) + { + this._items = items.ToList(); + } + + public event EventHandler? DataChanged; + + public Task> GetAsync(int offset, int count) + { + return Task.FromResult(this._items.Skip(offset).Take(count).ToList()); + } + + public void SetItems(IEnumerable items) + { + this._items = items.ToList(); + this.DataChanged?.Invoke(this, EventArgs.Empty); + } + } +}