From 4ccf522506bb43b5a8cc019ea8932ae09296a29e Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Mon, 14 Jul 2025 16:28:55 +0200 Subject: [PATCH 1/4] Changed css classes assignment in RenderPlaceholderRow --- .../src/QuickGrid.razor | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor index 5228ed1d2b00..49903935d26c 100644 --- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor +++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor @@ -79,7 +79,14 @@ @foreach (var col in _columns) { - @{ col.RenderPlaceholderContent(__builder, placeholderContext); } + var hasCustomPlaceholder = col.PlaceholderTemplate is not null; + var cssClass = hasCustomPlaceholder + ? "@ColumnClass(col)" + : "grid-cell-placeholder @ColumnClass(col)"; + + + @{ col.RenderPlaceholderContent(__builder, placeholderContext); } + } } From 0b24c80c597ab80492c07b889241e28449806c7a Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Tue, 15 Jul 2025 17:32:54 +0200 Subject: [PATCH 2/4] Tests for change --- .../test/E2ETest/Tests/VirtualizationTest.cs | 27 +++++++ .../test/testassets/BasicTestApp/Index.razor | 1 + .../VirtualizationQuickGrid.razor | 80 +++++++++++++++++++ .../ComplexValidationComponent.razor | 2 +- 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/Components/test/testassets/BasicTestApp/VirtualizationQuickGrid.razor diff --git a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs index cb1b221c117d..3f9c61cf6135 100644 --- a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs +++ b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs @@ -110,6 +110,33 @@ public void AlwaysFillsVisibleCapacity_Async() int GetPlaceholderCount() => Browser.FindElements(By.Id("async-placeholder")).Count; } + [Fact] + public void PlaceholdersHaveCorrectValue_Async() + { + Browser.MountTestComponent(); + + var finishLoadingButton = Browser.Exists(By.Id("finish-loading-button")); + + finishLoadingButton.Click(); //not waiting + + Browser.True(() => Browser.Exists(By.Id("loadDone")).Text != "111"); + + Browser.True(() => GetItemCount() > 0); + Browser.Equal(0, () => GetPlaceholderCount()); + + Browser.ExecuteJavaScript("const container = document.getElementById('async-container'); container.scrollTop = container.scrollHeight * 0.5;"); + + Browser.Equal(0, () => GetItemCount()); + Browser.True(() => GetPlaceholderCount() > 0); + + //test that the other placeholder has ... + Browser.Equal("LOADING DATA", () => Browser.Exists(By.CssSelector(".async-placeholder")).Text); + + + int GetItemCount() => Browser.FindElements(By.CssSelector("#async-container tbody tr:not(:has(.grid-cell-placeholder))")).Count; + int GetPlaceholderCount() => Browser.FindElements(By.CssSelector(".grid-cell-placeholder")).Count; + } + [Fact] public void RerendersWhenItemSizeShrinks_Sync() { diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 6e8d20b391a2..23262b501bd4 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -117,6 +117,7 @@ + diff --git a/src/Components/test/testassets/BasicTestApp/VirtualizationQuickGrid.razor b/src/Components/test/testassets/BasicTestApp/VirtualizationQuickGrid.razor new file mode 100644 index 000000000000..da78c9de0925 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/VirtualizationQuickGrid.razor @@ -0,0 +1,80 @@ +@using Microsoft.AspNetCore.Components.QuickGrid + + +@if (RendererInfo.IsInteractive) +{ + +} +
+ +@if (boolLoad != "no condition") +{ +

@boolLoad

+} + +
+ + + + + + LOADING DATA + + + +
+ +@code { + record DataItem(int Id, int SecondNum); + + QuickGrid asyncGrid; + + int asyncTotalItemCount = 200; + int asyncCancellationCount = 0; + string boolLoad = "no condition"; + TaskCompletionSource asyncTcs = new TaskCompletionSource(); + + private async ValueTask> GetItemsAsync(GridItemsProviderRequest request) + { + var loadingTask = asyncTcs.Task; + var registration = request.CancellationToken.Register(() => CancelLoadingAsync(request.CancellationToken)); + + await loadingTask; + + registration.Dispose(); + + var items = Enumerable.Range(request.StartIndex, request.Count ?? 200) + .Select(i => new DataItem(i, i * 2)) + .ToArray(); + + return GridItemsProviderResult.From(items, asyncTotalItemCount); + } + + async Task FinishLoadingAsync(int totalItemCount) + { + asyncTotalItemCount = totalItemCount; + asyncTcs.SetResult(); + asyncTcs = new TaskCompletionSource(); + + if (asyncGrid is not null) + { + await asyncGrid.RefreshDataAsync(); + boolLoad = "condition"; + } + else + { + boolLoad = "asyncGrid null"; + } + StateHasChanged(); + } + + void CancelLoadingAsync(System.Threading.CancellationToken cancellationToken) + { + asyncTcs.TrySetCanceled(cancellationToken); + asyncTcs = new TaskCompletionSource(); + + asyncCancellationCount++; + + StateHasChanged(); + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/ComplexValidationComponent.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/ComplexValidationComponent.razor index 2e4f4f958a69..e1f611d8d81c 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/ComplexValidationComponent.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/ComplexValidationComponent.razor @@ -3,7 +3,7 @@ @using Microsoft.AspNetCore.Components.Forms @if(RendererInfo.IsInteractive) { -

+

111

} @if (_invalid) From 56536c1792030e1cc5d86ce460a6dadecc400e81 Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Wed, 16 Jul 2025 16:06:58 +0200 Subject: [PATCH 3/4] Fix test and fix css styles --- .../src/QuickGrid.razor | 9 +- .../test/E2ETest/Tests/VirtualizationTest.cs | 25 +++--- .../VirtualizationQuickGrid.razor | 86 ++++++++++--------- 3 files changed, 61 insertions(+), 59 deletions(-) diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor index 49903935d26c..1013f0f78e6c 100644 --- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor +++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor @@ -80,13 +80,8 @@ @foreach (var col in _columns) { var hasCustomPlaceholder = col.PlaceholderTemplate is not null; - var cssClass = hasCustomPlaceholder - ? "@ColumnClass(col)" - : "grid-cell-placeholder @ColumnClass(col)"; - - - @{ col.RenderPlaceholderContent(__builder, placeholderContext); } - + var placeholderClass = hasCustomPlaceholder ? "" : "grid-cell-placeholder"; + @{ col.RenderPlaceholderContent(__builder, placeholderContext); } } } diff --git a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs index 3f9c61cf6135..3a6f9584fe0c 100644 --- a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs +++ b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs @@ -113,28 +113,33 @@ public void AlwaysFillsVisibleCapacity_Async() [Fact] public void PlaceholdersHaveCorrectValue_Async() { - Browser.MountTestComponent(); + var component = Browser.MountTestComponent(); var finishLoadingButton = Browser.Exists(By.Id("finish-loading-button")); - - finishLoadingButton.Click(); //not waiting - - Browser.True(() => Browser.Exists(By.Id("loadDone")).Text != "111"); + var startLoadingButton = Browser.Exists(By.Id("start-loading-button")); + //Load the initial data. + finishLoadingButton.Click(); Browser.True(() => GetItemCount() > 0); Browser.Equal(0, () => GetPlaceholderCount()); + //Start loading the second set of data to check for placeholders. + startLoadingButton.Click(); Browser.ExecuteJavaScript("const container = document.getElementById('async-container'); container.scrollTop = container.scrollHeight * 0.5;"); Browser.Equal(0, () => GetItemCount()); Browser.True(() => GetPlaceholderCount() > 0); - //test that the other placeholder has ... - Browser.Equal("LOADING DATA", () => Browser.Exists(By.CssSelector(".async-placeholder")).Text); - + Assert.Equal("\"…\"", Browser.ExecuteJavaScript(@" + const p = document.querySelector('td.async-id'); + return p ? getComputedStyle(p, '::after').content : null;")); + Assert.Equal("none", Browser.ExecuteJavaScript(@" + const p = document.querySelector('td.async-second'); + return p ? getComputedStyle(p, '::after').content : null;")); + Browser.Equal("LOADING DATA", () => Browser.Exists(By.CssSelector(".async-second .async-placeholder")).Text); - int GetItemCount() => Browser.FindElements(By.CssSelector("#async-container tbody tr:not(:has(.grid-cell-placeholder))")).Count; - int GetPlaceholderCount() => Browser.FindElements(By.CssSelector(".grid-cell-placeholder")).Count; + int GetItemCount() => Browser.FindElements(By.CssSelector("#async-container tbody td.async-id:not(.grid-cell-placeholder)")).Count; + int GetPlaceholderCount() => Browser.FindElements(By.CssSelector("#async-container tbody .async-id.grid-cell-placeholder")).Count; } [Fact] diff --git a/src/Components/test/testassets/BasicTestApp/VirtualizationQuickGrid.razor b/src/Components/test/testassets/BasicTestApp/VirtualizationQuickGrid.razor index da78c9de0925..58e7fc1eb7e8 100644 --- a/src/Components/test/testassets/BasicTestApp/VirtualizationQuickGrid.razor +++ b/src/Components/test/testassets/BasicTestApp/VirtualizationQuickGrid.razor @@ -3,78 +3,80 @@ @if (RendererInfo.IsInteractive) { - -} -
+ + -@if (boolLoad != "no condition") -{ -

@boolLoad

-} +
+ +
+ + + + + + LOADING DATA + + + +
-
- - - - - - LOADING DATA - - - -
+ + +} @code { record DataItem(int Id, int SecondNum); - QuickGrid asyncGrid; int asyncTotalItemCount = 200; int asyncCancellationCount = 0; - string boolLoad = "no condition"; + TaskCompletionSource asyncTcs = new TaskCompletionSource(); private async ValueTask> GetItemsAsync(GridItemsProviderRequest request) { var loadingTask = asyncTcs.Task; - var registration = request.CancellationToken.Register(() => CancelLoadingAsync(request.CancellationToken)); + var registration = request.CancellationToken.Register(() => CancelLoading(request.CancellationToken)); + + try + { + await loadingTask.WaitAsync(request.CancellationToken); - await loadingTask; + var items = Enumerable.Range(request.StartIndex, request.Count ?? 200) + .Select(i => new DataItem(i, i * 2)) + .ToArray(); - registration.Dispose(); + return GridItemsProviderResult.From(items, asyncTotalItemCount); + } + catch (OperationCanceledException) + { + throw; + } + finally + { + registration.Dispose(); + } + } - var items = Enumerable.Range(request.StartIndex, request.Count ?? 200) - .Select(i => new DataItem(i, i * 2)) - .ToArray(); - return GridItemsProviderResult.From(items, asyncTotalItemCount); + void StartNewAsyncLoad() + { + asyncTcs = new TaskCompletionSource(); + StateHasChanged(); } - async Task FinishLoadingAsync(int totalItemCount) + void FinishLoading(int totalItemCount) { asyncTotalItemCount = totalItemCount; asyncTcs.SetResult(); - asyncTcs = new TaskCompletionSource(); - - if (asyncGrid is not null) - { - await asyncGrid.RefreshDataAsync(); - boolLoad = "condition"; - } - else - { - boolLoad = "asyncGrid null"; - } StateHasChanged(); } - void CancelLoadingAsync(System.Threading.CancellationToken cancellationToken) + void CancelLoading(System.Threading.CancellationToken cancellationToken) { asyncTcs.TrySetCanceled(cancellationToken); asyncTcs = new TaskCompletionSource(); - asyncCancellationCount++; - StateHasChanged(); } } From 45752281e4ac80d3d2734e127bd5269e374b8d50 Mon Sep 17 00:00:00 2001 From: Daria Tiurina Date: Thu, 17 Jul 2025 16:40:50 +0200 Subject: [PATCH 4/4] fixes and feedback --- .../src/QuickGrid.razor | 6 +++--- .../src/Themes/Default.css | 2 +- src/Components/test/E2ETest/Tests/VirtualizationTest.cs | 7 ++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor index 1013f0f78e6c..3d61a1e2990f 100644 --- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor +++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor @@ -77,11 +77,11 @@ private void RenderPlaceholderRow(RenderTreeBuilder __builder, PlaceholderContext placeholderContext) { - @foreach (var col in _columns) + @foreach (var col in _columns) { var hasCustomPlaceholder = col.PlaceholderTemplate is not null; - var placeholderClass = hasCustomPlaceholder ? "" : "grid-cell-placeholder"; - @{ col.RenderPlaceholderContent(__builder, placeholderContext); } + var placeholderClass = hasCustomPlaceholder ? "grid-cell-placeholder" : "grid-cell-placeholder default"; + @{ col.RenderPlaceholderContent(__builder, placeholderContext); } } } diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Themes/Default.css b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Themes/Default.css index e11b358f9927..4b7313256ff8 100644 --- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Themes/Default.css +++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/Themes/Default.css @@ -75,7 +75,7 @@ border-radius: 0.3rem; } -.quickgrid[theme=default] > tbody > tr > td.grid-cell-placeholder:after { +.quickgrid[theme=default] > tbody > tr > td.grid-cell-placeholder.default:after { content: '\2026'; opacity: 0.75; } diff --git a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs index 3a6f9584fe0c..d45269677490 100644 --- a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs +++ b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs @@ -128,15 +128,16 @@ public void PlaceholdersHaveCorrectValue_Async() Browser.ExecuteJavaScript("const container = document.getElementById('async-container'); container.scrollTop = container.scrollHeight * 0.5;"); Browser.Equal(0, () => GetItemCount()); + int placeholderCount = GetPlaceholderCount(); Browser.True(() => GetPlaceholderCount() > 0); Assert.Equal("\"…\"", Browser.ExecuteJavaScript(@" - const p = document.querySelector('td.async-id'); + const p = document.querySelector('td.grid-cell-placeholder.default'); return p ? getComputedStyle(p, '::after').content : null;")); Assert.Equal("none", Browser.ExecuteJavaScript(@" - const p = document.querySelector('td.async-second'); + const p = document.querySelector('td.grid-cell-placeholder:not(.default)'); return p ? getComputedStyle(p, '::after').content : null;")); - Browser.Equal("LOADING DATA", () => Browser.Exists(By.CssSelector(".async-second .async-placeholder")).Text); + Browser.Equal("LOADING DATA", () => Browser.Exists(By.CssSelector(".grid-cell-placeholder .async-placeholder")).Text); int GetItemCount() => Browser.FindElements(By.CssSelector("#async-container tbody td.async-id:not(.grid-cell-placeholder)")).Count; int GetPlaceholderCount() => Browser.FindElements(By.CssSelector("#async-container tbody .async-id.grid-cell-placeholder")).Count;