diff --git a/components/AntDesign.csproj b/components/AntDesign.csproj index 10aef4621..634da1d4f 100644 --- a/components/AntDesign.csproj +++ b/components/AntDesign.csproj @@ -15,6 +15,7 @@ James Yeung James Yeung logo.png + CA2007 diff --git a/components/core/Extensions/EnumerableExtensions.cs b/components/core/Extensions/EnumerableExtensions.cs index 667730e2d..1c3ce7752 100644 --- a/components/core/Extensions/EnumerableExtensions.cs +++ b/components/core/Extensions/EnumerableExtensions.cs @@ -27,10 +27,7 @@ namespace AntDesign return; } - for (int i = 0; i < items.Count(); i++) - { - await func(items.ElementAt(i)); - } + foreach (var item in items) await func(item); } public static bool IsIn(this T source, params T[] array) diff --git a/components/table/Table.razor.RowData.cs b/components/table/Table.razor.RowData.cs index fc77fce6f..9f2fa4922 100644 --- a/components/table/Table.razor.RowData.cs +++ b/components/table/Table.razor.RowData.cs @@ -1,7 +1,7 @@ using System; -using AntDesign.TableModels; using System.Collections.Generic; using System.Linq; +using AntDesign.TableModels; namespace AntDesign { @@ -32,8 +32,8 @@ namespace AntDesign return; // Clear cached items that are not on current page - var currentPageCacheKeys = _selection.RowSelections.Select(x => x.CacheKey); - var deletedCaches = _dataSourceCache.Where(x => x.Value.PageIndex == PageIndex && !x.Key.IsIn(currentPageCacheKeys)); + var currentPageCacheKeys = _selection.RowSelections.Select(x => x.CacheKey).ToHashSet(); + var deletedCaches = _dataSourceCache.Where(x => x.Value.PageIndex == PageIndex && !currentPageCacheKeys.Contains(x.Key)).ToList(); var needInvokeChange = deletedCaches.Any(x => x.Value.Selected); deletedCaches.ForEach(x => _dataSourceCache.Remove(x)); diff --git a/components/table/Table.razor.cs b/components/table/Table.razor.cs index 50467d4f6..9ef59caf1 100644 --- a/components/table/Table.razor.cs +++ b/components/table/Table.razor.cs @@ -110,16 +110,19 @@ namespace AntDesign } else { - var query = _dataSource.AsQueryable(); - foreach (var sort in queryModel.SortModel) + if (_dataSource != null) { - sort.Sort(query); + var query = _dataSource.AsQueryable(); + foreach (var sort in queryModel.SortModel) + { + sort.Sort(query); + } + + query = query.Skip((PageIndex - 1) * PageSize).Take(PageSize); + queryModel.SetQueryableLambda(query); + + _showItems = query; } - - query = query.Skip((PageIndex - 1) * PageSize).Take(PageSize); - queryModel.SetQueryableLambda(query); - - _showItems = query; } StateHasChanged(); diff --git a/tests/$Recorded/AntDesign.Tests.Table.TableTestsCan_render_after_changes_to_the_dataSource.html b/tests/$Recorded/AntDesign.Tests.Table.TableTestsCan_render_after_changes_to_the_dataSource.html new file mode 100644 index 000000000..a11c14b77 --- /dev/null +++ b/tests/$Recorded/AntDesign.Tests.Table.TableTestsCan_render_after_changes_to_the_dataSource.html @@ -0,0 +1,96 @@ + +
+
+
+ + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+Joe +Doe
+
+
+
+ + + + + +
+ +
+
+ +
\ No newline at end of file diff --git a/tests/$Recorded/AntDesign.Tests.Table.TableTestsRenders_a_table_with_two_rows.html b/tests/$Recorded/AntDesign.Tests.Table.TableTestsRenders_a_table_with_two_rows.html new file mode 100644 index 000000000..3ae170b62 --- /dev/null +++ b/tests/$Recorded/AntDesign.Tests.Table.TableTestsRenders_a_table_with_two_rows.html @@ -0,0 +1,104 @@ + +
+
+
+ + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+John +Smith
+Jane +Doe
+
+
+
+ + + + + +
+ +
+
+ +
\ No newline at end of file diff --git a/tests/$Recorded/AntDesign.Tests.Table.TableTestsRenders_an_empty_table.html b/tests/$Recorded/AntDesign.Tests.Table.TableTestsRenders_an_empty_table.html new file mode 100644 index 000000000..e986283e9 --- /dev/null +++ b/tests/$Recorded/AntDesign.Tests.Table.TableTestsRenders_an_empty_table.html @@ -0,0 +1,99 @@ + +
+
+
+ + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + + + + + + + +
+ +

暂无数据

+ +
+
+
+
+
+ + + + + +
+ +
+
+ +
\ No newline at end of file diff --git a/tests/AntDesign.Tests.csproj b/tests/AntDesign.Tests.csproj index 0f659cb13..4499a97a2 100644 --- a/tests/AntDesign.Tests.csproj +++ b/tests/AntDesign.Tests.csproj @@ -20,4 +20,8 @@ + + + + diff --git a/tests/AntDesignTestBase.cs b/tests/AntDesignTestBase.cs index 93ecc9de8..26611f124 100644 --- a/tests/AntDesignTestBase.cs +++ b/tests/AntDesignTestBase.cs @@ -2,6 +2,7 @@ using Bunit; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; namespace AntDesign.Tests { diff --git a/tests/RecordedTestExtensions.cs b/tests/RecordedTestExtensions.cs new file mode 100644 index 000000000..beeba1b3e --- /dev/null +++ b/tests/RecordedTestExtensions.cs @@ -0,0 +1,93 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.RegularExpressions; +using Bunit; +using Microsoft.AspNetCore.Components; +using Xunit; +using Xunit.Sdk; + +namespace AntDesign.Tests +{ + public static class RecordedTestExtensions + { + private const string Style = + "\n"; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void RecordedMarkupMatches(this IRenderedComponent component) where T : notnull, IComponent + { + static string Cleanup(string value) + { + value = Regex.Replace(value, "id=\"ant-blazor-.+?\"", "id:ignore"); + value = Regex.Replace(value, "blazor:elementreference=\".+?\"", "blazor:elementreference:ignore"); + + return value; + } + + + var caller = new StackTrace().GetFrame(1)?.GetMethod(); + RecordedMarkupMatches( + component.Markup, + caller, + expected => component.MarkupMatches(expected), + Cleanup + ); + } + + public static void RecordedMarkupMatches(string markup) + { + var caller = new StackTrace().GetFrame(1)?.GetMethod(); + RecordedMarkupMatches( + markup, + caller, + expected => Assert.Equal(markup, expected), + m => m + ); + } + + private static void RecordedMarkupMatches(string markup, MethodBase caller, Action assert, + Func transform) + { + if (caller == null) + throw new XunitException("Cannot find caller from StackTrace."); + + if (caller.ReflectedType == null) + throw new XunitException("Cannot access ReflectedType for the method."); + + // Here we make an assumption that project is not following an unconventional directory structure. + var expectedPath = $"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}"; + var parts = caller.ReflectedType.Assembly.Location.Split(expectedPath); + if (parts.Length == 1) + throw new XunitException($"Path does not include ${expectedPath}"); + + var recordedTestsPath = Path.Combine(parts[0], "$Recorded"); + + if (!Directory.Exists(recordedTestsPath)) + Directory.CreateDirectory(recordedTestsPath); + + var sanitisedFileName = Path.GetInvalidFileNameChars() + .Aggregate(new StringBuilder($"{caller.ReflectedType}{caller.Name}.html"), + (builder, c) => builder.Replace(c, '_')) + .ToString(); + + var testFile = Path.Combine(recordedTestsPath, sanitisedFileName); + + if (File.Exists(testFile)) + { + var expected = File.ReadAllText(testFile); + assert(expected.Replace(Style, "")); + } + else + { + File.WriteAllText(testFile, Style + transform(markup)); + throw new XunitException( + "Test file for comparison was not found, so a new one was created. Please review the file before re-running the test."); + } + } + } +} diff --git a/tests/table/TableTests.cs b/tests/table/TableTests.cs new file mode 100644 index 000000000..0a99b1c41 --- /dev/null +++ b/tests/table/TableTests.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using Bunit; +using Microsoft.AspNetCore.Components; +using Xunit; + +namespace AntDesign.Tests.Table +{ + public class TableTests : AntDesignTestBase + { + class Person + { + public int Id { get; set; } + public string Name { get; set; } + public string Surname { get; set; } + } + + private IRenderedComponent> CreatePersonsTable( + IReadOnlyList persons, + Action>> callback = null, + bool enableSelection = false) + { + return Context.RenderComponent>(x => + { + x + .Add(b => b.DataSource, persons) + .Add(b => b.ChildContent, p => + { + var selection = new ComponentParameterBuilder() + .Add(q => q.Key, p.Id.ToString()) + .Build() + .ToComponentRenderFragment(); + + var nameCol = new ComponentParameterBuilder>() + .Add(q => q.Field, p.Name) + .Build() + .ToComponentRenderFragment>(); + + var surnameCol = new ComponentParameterBuilder>() + .Add(q => q.Field, p.Surname) + .Build() + .ToComponentRenderFragment>(); + + return builder => + { + if (enableSelection) selection(builder); + nameCol(builder); + surnameCol(builder); + }; + } + ); + + callback?.Invoke(x); + } + ); + } + + [Fact] + public void Renders_an_empty_table() + { + var persons = Array.Empty(); + + var cut = CreatePersonsTable(persons); + + cut.RecordedMarkupMatches(); + } + + [Fact] + public void Renders_a_table_with_two_rows() + { + var persons = new[] + { + new Person {Id = 1, Name = "John", Surname = "Smith"}, + new Person {Id = 2, Name = "Jane", Surname = "Doe"} + }; + + var cut = CreatePersonsTable(persons); + + cut.RecordedMarkupMatches(); + } + + [Fact] + public void Can_render_after_changes_to_the_dataSource() + { + var persons = new List + { + new Person {Id = 1, Name = "John", Surname = "Smith"}, + new Person {Id = 2, Name = "Jane", Surname = "Doe"}, + new Person {Id = 3, Name = "Joe", Surname = "Doe"} + }; + + var cut = CreatePersonsTable(persons, b => b + .Add(q => q.PageSize, 1) + .Add(q => q.PageIndex, 3) + ); + + persons.RemoveAt(0); + + cut.SetParametersAndRender(b => b.Add(q => q.DataSource, persons)); + + cut.RecordedMarkupMatches(); + } + } +}