更新
更旧
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
using BasicTestApp;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.Extensions;

Steve Sanderson
已提交
using OpenQA.Selenium.Support.UI;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests;
public class VirtualizationTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
public VirtualizationTest(
BrowserFixture browserFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
protected override void InitializeAsyncCore()
{
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
}
[Fact]
public void AlwaysFillsVisibleCapacity_Sync()
{
Browser.MountTestComponent<VirtualizationComponent>();
var topSpacer = Browser.Exists(By.Id("sync-container")).FindElement(By.TagName("div"));

Mackinnon Buck
已提交
var expectedInitialSpacerStyle = "height: 0px; flex-shrink: 0;";
// Wait until items have been rendered.
Browser.True(() => (initialItemCount = GetItemCount()) > 0);
Browser.Equal(expectedInitialSpacerStyle, () => topSpacer.GetAttribute("style"));
// Scroll halfway.
Browser.ExecuteJavaScript("const container = document.getElementById('sync-container');container.scrollTop = container.scrollHeight * 0.5;");
// Validate that we get the same item count after scrolling halfway.
Browser.Equal(initialItemCount, GetItemCount);
Browser.NotEqual(expectedInitialSpacerStyle, () => topSpacer.GetAttribute("style"));
// Scroll to the bottom.
Browser.ExecuteJavaScript("const container = document.getElementById('sync-container');container.scrollTop = container.scrollHeight;");
// Validate that we get the same item count after scrolling to the bottom.
Browser.Equal(initialItemCount, GetItemCount);
Browser.NotEqual(expectedInitialSpacerStyle, () => topSpacer.GetAttribute("style"));
int GetItemCount() => Browser.FindElements(By.Id("sync-item")).Count;
}
[Fact]
public void AlwaysFillsVisibleCapacity_Async()
{
Browser.MountTestComponent<VirtualizationComponent>();
var finishLoadingButton = Browser.Exists(By.Id("finish-loading-button"));
// Check that no items or placeholders are visible.
// No data fetches have happened so we don't know how many items there are.
Browser.Equal(0, GetItemCount);
Browser.Equal(0, GetPlaceholderCount);
// Load the initial set of items.
finishLoadingButton.Click();
// Validate that items appear and placeholders aren't rendered.
Browser.True(() => (initialItemCount = GetItemCount()) > 0);
Browser.Equal(0, GetPlaceholderCount);
// Scroll halfway.
Browser.ExecuteJavaScript("const container = document.getElementById('async-container');container.scrollTop = container.scrollHeight * 0.5;");
// Validate that items are replaced by the same number of placeholders.
Browser.Equal(0, GetItemCount);
Browser.Equal(initialItemCount, GetPlaceholderCount);
// Load the new set of items.
finishLoadingButton.Click();
// Validate that the placeholders are replaced by the same number of items.
Browser.Equal(initialItemCount, GetItemCount);
Browser.Equal(0, GetPlaceholderCount);
// Scroll to the bottom.
Browser.ExecuteJavaScript("const container = document.getElementById('async-container');container.scrollTop = container.scrollHeight;");
// Validate that items are replaced by the same number of placeholders.
Browser.Equal(0, GetItemCount);
Browser.Equal(initialItemCount, GetPlaceholderCount);
// Load the new set of items.
finishLoadingButton.Click();
// Validate that the placeholders are replaced by the same number of items.
Browser.Equal(initialItemCount, GetItemCount);
Browser.Equal(0, GetPlaceholderCount);
int GetItemCount() => Browser.FindElements(By.Id("async-item")).Count;
int GetPlaceholderCount() => Browser.FindElements(By.Id("async-placeholder")).Count;
}
[Fact]
public void RerendersWhenItemSizeShrinks_Sync()
{
Browser.MountTestComponent<VirtualizationComponent>();
int initialItemCount = 0;
// Wait until items have been rendered.
Browser.True(() => (initialItemCount = GetItemCount()) > 0);
var itemSizeInput = Browser.Exists(By.Id("item-size-input"));
// Change the item size.
itemSizeInput.SendKeys("\b\b\b10\n");
// Validate that the list has been re-rendered to show more items.
Browser.True(() => GetItemCount() > initialItemCount);
int GetItemCount() => Browser.FindElements(By.Id("sync-item")).Count;
}
[Fact]
public void RerendersWhenItemSizeShrinks_Async()
{
Browser.MountTestComponent<VirtualizationComponent>();
var finishLoadingButton = Browser.Exists(By.Id("finish-loading-button"));
// Load the initial set of items.
finishLoadingButton.Click();
// Validate that items appear and placeholders aren't rendered.
Browser.True(() => (initialItemCount = GetItemCount()) > 0);
Browser.Equal(0, GetPlaceholderCount);
var itemSizeInput = Browser.Exists(By.Id("item-size-input"));
// Change the item size.
itemSizeInput.SendKeys("\b\b\b10\n");
// Validate that the same number of loaded items is rendered.
Browser.Equal(initialItemCount, GetItemCount);
Browser.True(() => GetPlaceholderCount() > 0);
// Load the new set of items.
finishLoadingButton.Click();
// Validate that the placeholders have been replaced with more loaded items.
Browser.True(() => GetItemCount() > initialItemCount);
Browser.Equal(0, GetPlaceholderCount);
int GetItemCount() => Browser.FindElements(By.Id("async-item")).Count;
int GetPlaceholderCount() => Browser.FindElements(By.Id("async-placeholder")).Count;
}
[Fact]
public void CancelsOutdatedRefreshes_Async()
{
Browser.MountTestComponent<VirtualizationComponent>();
var cancellationCount = Browser.Exists(By.Id("cancellation-count"));
var finishLoadingButton = Browser.Exists(By.Id("finish-loading-button"));
var js = (IJavaScriptExecutor)Browser;
// Load the initial set of items.
finishLoadingButton.Click();
// Validate that there are no initial cancellations.
Browser.Equal("0", () => cancellationCount.Text);
// Validate that scrolling causes cancellations
for (var y = 1000; y <= 5000; y += 1000)
{
js.ExecuteScript($"document.getElementById('async-container').scrollTo({{ top: {y} }})");
Browser.Equal(y, () => (long)js.ExecuteScript("return document.getElementById('async-container').scrollTop"));
Browser.True(() => int.Parse(cancellationCount.Text, CultureInfo.InvariantCulture) > 0);
}
[Fact]
public void CanUseViewportAsContainer()
{
Browser.MountTestComponent<VirtualizationComponent>();

Mackinnon Buck
已提交
var expectedInitialSpacerStyle = "height: 0px; flex-shrink: 0;";
var topSpacer = Browser.Exists(By.Id("viewport-as-root")).FindElement(By.TagName("div"));
Browser.ExecuteJavaScript("const element = document.getElementById('viewport-as-root'); element.scrollIntoView();");
// Validate that the top spacer has a height of zero.
Browser.Equal(expectedInitialSpacerStyle, () => topSpacer.GetAttribute("style"));
Browser.ExecuteJavaScript("window.scrollTo(0, document.body.scrollHeight);");
// Validate that the scroll event completed successfully
var lastElement = Browser.Exists(By.Id("999"));
Browser.True(() => lastElement.Displayed);
// Validate that the top spacer has expanded.
Browser.NotEqual(expectedInitialSpacerStyle, () => topSpacer.GetAttribute("style"));
}

Steve Sanderson
已提交
[Fact]
public async Task ToleratesIncorrectItemSize()
{
Browser.MountTestComponent<VirtualizationComponent>();
var topSpacer = Browser.Exists(By.Id("incorrect-size-container")).FindElement(By.TagName("div"));

Mackinnon Buck
已提交
var expectedInitialSpacerStyle = "height: 0px; flex-shrink: 0;";
// Wait until items have been rendered.
Browser.True(() => GetItemCount() > 0);
Browser.Equal(expectedInitialSpacerStyle, () => topSpacer.GetAttribute("style"));
// Scroll slowly, in increments of 50px at a time. At one point this would trigger a bug
// due to the incorrect item size, whereby it would not realise it's necessary to show more
// items because the first time the spacer became visible, the size calculation said that
// we're already showing all the items we need to show.
for (var pos = 0; pos < 1000; pos += 50)
Browser.ExecuteJavaScript($"document.getElementById('incorrect-size-container').scrollTop = {pos};");
await Task.Delay(200);
// Validate that the top spacer did change
Browser.NotEqual(expectedInitialSpacerStyle, () => topSpacer.GetAttribute("style"));
int GetItemCount() => Browser.FindElements(By.ClassName("incorrect-size-item")).Count;
}
[Fact]
public void CanRenderHtmlTable()
{
Browser.MountTestComponent<VirtualizationTable>();
var expectedInitialSpacerStyle = "height: 0px; flex-shrink: 0;";
var topSpacer = Browser.Exists(By.CssSelector("#virtualized-table > tbody > :first-child"));
var bottomSpacer = Browser.Exists(By.CssSelector("#virtualized-table > tbody > :last-child"));
// We can override the tag name of the spacer
Assert.Equal("tr", topSpacer.TagName.ToLowerInvariant());
Assert.Equal("tr", bottomSpacer.TagName.ToLowerInvariant());
Assert.Contains(expectedInitialSpacerStyle, topSpacer.GetAttribute("style"));
// Check scrolling document element works
Browser.DoesNotExist(By.Id("row-999"));
Browser.ExecuteJavaScript("window.scrollTo(0, document.body.scrollHeight);");
var lastElement = Browser.Exists(By.Id("row-999"));
Browser.True(() => lastElement.Displayed);
// Validate that the top spacer has expanded, and bottom one has collapsed
Browser.False(() => topSpacer.GetAttribute("style").Contains(expectedInitialSpacerStyle));
Assert.Contains(expectedInitialSpacerStyle, bottomSpacer.GetAttribute("style"));
}
[Fact]
public void CanMutateDataInPlace_Sync()
{
Browser.MountTestComponent<VirtualizationDataChanges>();
// Initial data
var container = Browser.Exists(By.Id("using-items"));
Browser.Collection(() => GetPeopleNames(container),
name => Assert.Equal("Person 1", name),
name => Assert.Equal("Person 2", name),
name => Assert.Equal("Person 3", name));
// Mutate one of them
var itemToMutate = container.FindElements(By.ClassName("person"))[1];
itemToMutate.FindElement(By.TagName("button")).Click();
// See changes
Browser.Collection(() => GetPeopleNames(container),
name => Assert.Equal("Person 1", name),
name => Assert.Equal("Person 2 MUTATED", name),
name => Assert.Equal("Person 3", name));
}
[Fact]
public void CanMutateDataInPlace_Async()
{
Browser.MountTestComponent<VirtualizationDataChanges>();
// Initial data
var container = Browser.Exists(By.Id("using-itemsprovider"));
Browser.Collection(() => GetPeopleNames(container),
name => Assert.Equal("Person 1", name),
name => Assert.Equal("Person 2", name),
name => Assert.Equal("Person 3", name));
// Mutate one of them
var itemToMutate = container.FindElements(By.ClassName("person"))[1];
itemToMutate.FindElement(By.TagName("button")).Click();
// See changes
Browser.Collection(() => GetPeopleNames(container),
name => Assert.Equal("Person 1", name),
name => Assert.Equal("Person 2 MUTATED", name),
name => Assert.Equal("Person 3", name));
}
[Fact]
public void CanChangeDataCount_Sync()
{
Browser.MountTestComponent<VirtualizationDataChanges>();
// Initial data
var container = Browser.Exists(By.Id("using-items"));
Browser.Collection(() => GetPeopleNames(container),
name => Assert.Equal("Person 1", name),
name => Assert.Equal("Person 2", name),
name => Assert.Equal("Person 3", name));
// Add another item
Browser.Exists(By.Id("add-person-to-fixed-list")).Click();
// See changes
Browser.Collection(() => GetPeopleNames(container),
name => Assert.Equal("Person 1", name),
name => Assert.Equal("Person 2", name),
name => Assert.Equal("Person 3", name),
name => Assert.Equal("Person 4", name));
}
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
[Fact]
public void CanChangeDataCount_Async()
{
Browser.MountTestComponent<VirtualizationDataChanges>();
// Initial data
var container = Browser.Exists(By.Id("using-itemsprovider"));
Browser.Collection(() => GetPeopleNames(container),
name => Assert.Equal("Person 1", name),
name => Assert.Equal("Person 2", name),
name => Assert.Equal("Person 3", name));
// Add another item
Browser.Exists(By.Id("add-person-to-itemsprovider")).Click();
// Initially this has no effect because we don't re-query the provider until told to do so
Browser.Collection(() => GetPeopleNames(container),
name => Assert.Equal("Person 1", name),
name => Assert.Equal("Person 2", name),
name => Assert.Equal("Person 3", name));
// Request refresh
Browser.Exists(By.Id("refresh-itemsprovider")).Click();
// See changes
Browser.Collection(() => GetPeopleNames(container),
name => Assert.Equal("Person 1", name),
name => Assert.Equal("Person 2", name),
name => Assert.Equal("Person 3", name),
name => Assert.Equal("Person 4", name));
}

Steve Sanderson
已提交
[Fact]
public void CanRefreshItemsProviderResultsInPlace()
{
Browser.MountTestComponent<VirtualizationDataChanges>();
// Mutate the data
var container = Browser.Exists(By.Id("using-itemsprovider"));
var itemToMutate = container.FindElements(By.ClassName("person"))[1];
itemToMutate.FindElement(By.TagName("button")).Click();
// Verify the mutation was applied
Browser.Collection(() => GetPeopleNames(container),
name => Assert.Equal("Person 1", name),
name => Assert.Equal("Person 2 MUTATED", name),
name => Assert.Equal("Person 3", name));
// Refresh and verify the mutation was reverted
Browser.Exists(By.Id("refresh-itemsprovider")).Click();
Browser.Collection(() => GetPeopleNames(container),
name => Assert.Equal("Person 1", name),
name => Assert.Equal("Person 2", name),
name => Assert.Equal("Person 3", name));
}

Steve Sanderson
已提交
[Theory]
[InlineData("simple-scroll-horizontal")]
[InlineData("complex-scroll-horizontal")]
[InlineData("simple-scroll-horizontal-on-parent")]
[InlineData("complex-scroll-horizontal-on-parent")]
[InlineData("complex-scroll-horizontal-on-tbody")]
[InlineData("simple-display-table-scroll-horizontal")]
[InlineData("complex-display-table-scroll-horizontal")]
public void CanLoadNewDataWithHorizontalScrollToRight(string containerId)
{
Browser.MountTestComponent<VirtualizationDataChanges>();
var dataSetLengthSelector = new SelectElement(Browser.Exists(By.Id("large-dataset-length")));
var dataSetLengthLastRendered = () => int.Parse(Browser.FindElement(By.Id("large-dataset-length-lastrendered")).Text, CultureInfo.InvariantCulture);
var container = Browser.Exists(By.Id(containerId));
// Scroll to the end of a medium list
dataSetLengthSelector.SelectByText("1000");
Browser.Equal(1000, dataSetLengthLastRendered);
Browser.True(() =>
ScrollLeftToEnd(Browser, container);
ScrollTopToEnd(Browser, container);
return GetPeopleNames(container).Contains("Person 1000");
});

Steve Sanderson
已提交

Steve Sanderson
已提交
{
ScrollLeftToEnd(Browser, container);
ScrollTopToBeginning(Browser, container);
return GetPeopleNames(container).Contains("Person 1");
});
}
[Theory]
[InlineData("simple-scroll-horizontal")]
[InlineData("complex-scroll-horizontal")]
[InlineData("simple-scroll-horizontal-on-parent")]
[InlineData("complex-scroll-horizontal-on-parent")]
[InlineData("complex-scroll-horizontal-on-tbody")]
[InlineData("simple-display-table-scroll-horizontal")]
[InlineData("complex-display-table-scroll-horizontal")]
[InlineData("removing-many")]
public void CanExpandDataSetAndRetainScrollPosition(string containerId)
{
Browser.MountTestComponent<VirtualizationDataChanges>();
var dataSetLengthSelector = new SelectElement(Browser.Exists(By.Id("large-dataset-length")));
var dataSetLengthLastRendered = () => int.Parse(Browser.FindElement(By.Id("large-dataset-length-lastrendered")).Text, CultureInfo.InvariantCulture);
var container = Browser.Exists(By.Id(containerId));
// Scroll to the end of a medium list
dataSetLengthSelector.SelectByText("1000");
Browser.Equal(1000, dataSetLengthLastRendered);
Browser.True(() =>
ScrollTopToEnd(Browser, container);
return GetPeopleNames(container).Contains("Person 1000");
});
// Expand the data set
dataSetLengthSelector.SelectByText("100000");
Browser.Equal(100000, dataSetLengthLastRendered);
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
// See that the old data is still visible, because the scroll position is preserved as a pixel count,
// not a scroll percentage
Browser.True(() => GetPeopleNames(container).Contains("Person 1000"));
}
[Theory]
[InlineData("simple-scroll-horizontal")]
[InlineData("complex-scroll-horizontal")]
[InlineData("simple-scroll-horizontal-on-parent")]
[InlineData("complex-scroll-horizontal-on-parent")]
[InlineData("complex-scroll-horizontal-on-tbody")]
[InlineData("simple-display-table-scroll-horizontal")]
[InlineData("complex-display-table-scroll-horizontal")]
[InlineData("removing-many")]
public void CanHandleDataSetShrinkingWithExistingOffsetAlreadyBeyondNewListEnd(string containerId)
{
// Represents https://github.com/dotnet/aspnetcore/issues/37245
Browser.MountTestComponent<VirtualizationDataChanges>();
var dataSetLengthSelector = new SelectElement(Browser.Exists(By.Id("large-dataset-length")));
var dataSetLengthLastRendered = () => int.Parse(Browser.FindElement(By.Id("large-dataset-length-lastrendered")).Text, CultureInfo.InvariantCulture);
var container = Browser.Exists(By.Id(containerId));
// Scroll to the end of a very long list
dataSetLengthSelector.SelectByText("100000");
Browser.Equal(100000, dataSetLengthLastRendered);
Browser.True(() =>
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
ScrollTopToEnd(Browser, container);
return GetPeopleNames(container).Contains("Person 100000");
});
// Now make the dataset much shorter
// We should automatically have the scroll position reduced to the new maximum
// Because the new data set is *so much* shorter than the previous one, if bug #37245 were still here,
// this would take over 30 minutes so the test would fail
dataSetLengthSelector.SelectByText("25");
Browser.Equal(25, dataSetLengthLastRendered);
Browser.True(() => GetPeopleNames(container).Contains("Person 25"));
}
private string[] GetPeopleNames(IWebElement container)
{
var peopleElements = container.FindElements(By.CssSelector(".person span"));
return peopleElements.Select(element => element.Text).ToArray();
}
private static void ScrollTopToEnd(IWebDriver browser, IWebElement elem)
{
var js = (IJavaScriptExecutor)browser;
js.ExecuteScript("arguments[0].scrollTop = arguments[0].scrollHeight", elem);
}
private static void ScrollTopToBeginning(IWebDriver browser, IWebElement elem)
{
var js = (IJavaScriptExecutor)browser;
js.ExecuteScript("arguments[0].scrollTop = 0", elem);
}
private static void ScrollLeftToEnd(IWebDriver Browser, IWebElement elem)
{
var js = (IJavaScriptExecutor)Browser;
js.ExecuteScript("arguments[0].scrollLeft = arguments[0].scrollWidth", elem);