using Common.ExpandFun; namespace Common.Test; /// /// CollectionExpand 集合扩展测试 /// 展示分页、树形结构、批处理等集合操作功能 /// [TestClass] public class CollectionExpandTests { #region 测试数据类 private class TestItem { public int Id { get; set; } public string Name { get; set; } = ""; public int? ParentId { get; set; } } #endregion #region 分页测试 [TestMethod] public void ToPagedResult_BasicPaging_ReturnsCorrectPage() { // Arrange var items = Enumerable.Range(1, 100).ToList(); // Act var page1 = items.ToPagedResult(1, 10); var page2 = items.ToPagedResult(2, 10); // Assert Assert.AreEqual(1, page1.PageIndex); Assert.AreEqual(10, page1.PageSize); Assert.AreEqual(100, page1.TotalCount); Assert.AreEqual(10, page1.TotalPages); Assert.AreEqual(10, page1.Items.Count); Assert.AreEqual(1, page1.Items.First()); Assert.AreEqual(10, page1.Items.Last()); Assert.AreEqual(11, page2.Items.First()); Assert.AreEqual(20, page2.Items.Last()); } [TestMethod] public void ToPagedResult_LastPage_ReturnsPartialPage() { // Arrange var items = Enumerable.Range(1, 25).ToList(); // Act var lastPage = items.ToPagedResult(3, 10); // Assert Assert.AreEqual(3, lastPage.PageIndex); Assert.AreEqual(5, lastPage.Items.Count); // 最后一页只有 5 个元素 Assert.AreEqual(21, lastPage.Items.First()); Assert.AreEqual(25, lastPage.Items.Last()); } [TestMethod] public void ToPagedResult_InvalidPageIndex_ClampsToValidRange() { // Arrange var items = Enumerable.Range(1, 50).ToList(); // Act var zeroPage = items.ToPagedResult(0, 10); // 应该被修正为第 1 页 var negativePage = items.ToPagedResult(-1, 10); // 应该被修正为第 1 页 // Assert Assert.AreEqual(1, zeroPage.PageIndex); Assert.AreEqual(1, negativePage.PageIndex); } [TestMethod] public void PagedResult_Navigation_PropertiesWorkCorrectly() { // Arrange var items = Enumerable.Range(1, 50).ToList(); // Act var firstPage = items.ToPagedResult(1, 10); var middlePage = items.ToPagedResult(3, 10); var lastPage = items.ToPagedResult(5, 10); // Assert Assert.IsFalse(firstPage.HasPreviousPage); Assert.IsTrue(firstPage.HasNextPage); Assert.IsTrue(middlePage.HasPreviousPage); Assert.IsTrue(middlePage.HasNextPage); Assert.IsTrue(lastPage.HasPreviousPage); Assert.IsFalse(lastPage.HasNextPage); } #endregion #region 树形结构测试 [TestMethod] public void ToTree_FlatListToTree_BuildsCorrectHierarchy() { // Arrange: 创建平铺的列表 var items = new List { new() { Id = 1, Name = "根节点1", ParentId = null }, new() { Id = 2, Name = "根节点2", ParentId = null }, new() { Id = 3, Name = "子节点1-1", ParentId = 1 }, new() { Id = 4, Name = "子节点1-2", ParentId = 1 }, new() { Id = 5, Name = "子节点2-1", ParentId = 2 }, new() { Id = 6, Name = "孙节点1-1-1", ParentId = 3 } }; // Act var tree = items.ToTree( x => x.Id, x => x.ParentId, null ); // Assert Assert.AreEqual(2, tree.Count); // 两个根节点 Assert.AreEqual("根节点1", tree[0].Data.Name); Assert.AreEqual(2, tree[0].Children.Count); // 根节点1 有两个子节点 Assert.AreEqual("子节点1-1", tree[0].Children[0].Data.Name); Assert.AreEqual(1, tree[0].Children[0].Children.Count); // 子节点1-1 有一个孙节点 Assert.AreEqual("孙节点1-1-1", tree[0].Children[0].Children[0].Data.Name); } [TestMethod] public void ToTree_BuildsCorrectLevels() { // Arrange var items = new List { new() { Id = 1, Name = "根", ParentId = null }, new() { Id = 2, Name = "子", ParentId = 1 }, new() { Id = 3, Name = "孙", ParentId = 2 } }; // Act var tree = items.ToTree(x => x.Id, x => x.ParentId); // Assert Assert.AreEqual(0, tree[0].Level); // 根节点 Level = 0 Assert.AreEqual(1, tree[0].Children[0].Level); // 子节点 Level = 1 Assert.AreEqual(2, tree[0].Children[0].Children[0].Level); // 孙节点 Level = 2 } [TestMethod] public void ToFlatList_TreeToFlatList_PreservesAllNodes() { // Arrange var items = new List { new() { Id = 1, Name = "根", ParentId = null }, new() { Id = 2, Name = "子1", ParentId = 1 }, new() { Id = 3, Name = "子2", ParentId = 1 }, new() { Id = 4, Name = "孙", ParentId = 2 } }; var tree = items.ToTree(x => x.Id, x => x.ParentId); // Act var flatList = tree.ToFlatList(); // Assert Assert.AreEqual(4, flatList.Count); Assert.AreEqual("根", flatList[0].Name); Assert.AreEqual("子1", flatList[1].Name); Assert.AreEqual("孙", flatList[2].Name); // 深度优先遍历 Assert.AreEqual("子2", flatList[3].Name); } [TestMethod] public void TraverseTree_ExecutesActionForAllNodes() { // Arrange var items = new List { new() { Id = 1, Name = "A", ParentId = null }, new() { Id = 2, Name = "B", ParentId = 1 }, new() { Id = 3, Name = "C", ParentId = 1 } }; var tree = items.ToTree(x => x.Id, x => x.ParentId); var visitedNames = new List(); // Act tree.TraverseTree(node => visitedNames.Add(node.Data.Name)); // Assert Assert.AreEqual(3, visitedNames.Count); CollectionAssert.Contains(visitedNames, "A"); CollectionAssert.Contains(visitedNames, "B"); CollectionAssert.Contains(visitedNames, "C"); } #endregion #region 批处理测试 [TestMethod] public void Batch_DividesIntoCorrectBatches() { // Arrange var items = Enumerable.Range(1, 10).ToList(); // Act var batches = items.Batch(3).ToList(); // Assert Assert.AreEqual(4, batches.Count); // 10 个元素,每批 3 个,共 4 批 Assert.AreEqual(3, batches[0].Count()); Assert.AreEqual(3, batches[1].Count()); Assert.AreEqual(3, batches[2].Count()); Assert.AreEqual(1, batches[3].Count()); // 最后一批只有 1 个 } [TestMethod] public void Batch_EmptyCollection_ReturnsEmpty() { // Arrange var items = new List(); // Act var batches = items.Batch(5).ToList(); // Assert Assert.AreEqual(0, batches.Count); } [TestMethod] public void ForEachBatch_ExecutesActionForEachBatch() { // Arrange var items = Enumerable.Range(1, 25).ToList(); var processedBatches = new List(); // Act items.ForEachBatch(10, batch => { processedBatches.Add(batch.Count()); }); // Assert Assert.AreEqual(3, processedBatches.Count); Assert.AreEqual(10, processedBatches[0]); Assert.AreEqual(10, processedBatches[1]); Assert.AreEqual(5, processedBatches[2]); } #endregion #region 空集合安全操作测试 [TestMethod] public void OrEmpty_NullCollection_ReturnsEmptyCollection() { // Arrange List? nullList = null; // Act var result = nullList.OrEmpty(); // Assert Assert.IsNotNull(result); Assert.AreEqual(0, result.Count()); } [TestMethod] public void OrDefault_EmptyCollection_ReturnsDefaultValue() { // Arrange var emptyList = new List(); var defaultList = new List { 1, 2, 3 }; // Act var result = emptyList.OrDefault(defaultList); // Assert Assert.AreEqual(3, result.Count()); CollectionAssert.AreEqual(defaultList, result.ToList()); } [TestMethod] public void IsNullOrEmpty_VariousInputs_ReturnsCorrectResult() { // Arrange List? nullList = null; var emptyList = new List(); var nonEmptyList = new List { 1 }; // Act & Assert Assert.IsTrue(nullList.IsNullOrEmpty()); Assert.IsTrue(emptyList.IsNullOrEmpty()); Assert.IsFalse(nonEmptyList.IsNullOrEmpty()); } [TestMethod] public void HasItems_VariousInputs_ReturnsCorrectResult() { // Arrange List? nullList = null; var emptyList = new List(); var nonEmptyList = new List { 1 }; // Act & Assert Assert.IsFalse(nullList.HasItems()); Assert.IsFalse(emptyList.HasItems()); Assert.IsTrue(nonEmptyList.HasItems()); } #endregion #region DistinctBy 测试 [TestMethod] public void DistinctBy_RemovesDuplicatesByKey() { // Arrange var items = new List { new() { Id = 1, Name = "A" }, new() { Id = 2, Name = "B" }, new() { Id = 3, Name = "A" }, // 重复的 Name new() { Id = 4, Name = "C" } }; // Act (.NET 6+ 内置 DistinctBy) var distinct = items.DistinctBy(x => x.Name).ToList(); // Assert Assert.AreEqual(3, distinct.Count); Assert.AreEqual(1, distinct[0].Id); // 保留第一个 "A" Assert.AreEqual(2, distinct[1].Id); Assert.AreEqual(4, distinct[2].Id); } #endregion #region 其他实用扩展测试 [TestMethod] public void ForEach_ExecutesActionForAllElements() { // Arrange var items = new List { 1, 2, 3 }; var sum = 0; // Act items.ForEach(x => sum += x); // Assert Assert.AreEqual(6, sum); } [TestMethod] public void ForEach_WithIndex_ProvidesCorrectIndex() { // Arrange var items = new List { "A", "B", "C" }; var results = new List(); // Act items.ForEach((item, index) => results.Add($"{index}:{item}")); // Assert CollectionAssert.AreEqual(new[] { "0:A", "1:B", "2:C" }, results); } [TestMethod] public void JoinString_ConcatenatesWithSeparator() { // Arrange var items = new[] { "apple", "banana", "cherry" }; // Act var result = items.JoinString(", "); // Assert Assert.AreEqual("apple, banana, cherry", result); } [TestMethod] public void Shuffle_ChangesOrder() { // Arrange var items = Enumerable.Range(1, 100).ToList(); var original = items.ToList(); // Act var shuffled = items.Shuffle().ToList(); // Assert: 打乱后的顺序应该不同(理论上有极小概率相同) Assert.AreEqual(original.Count, shuffled.Count); // 检查是否至少有一些元素位置变化 var differentPositions = original.Where((item, index) => shuffled[index] != item).Count(); Assert.IsTrue(differentPositions > 10); // 至少 10% 的元素位置不同 } [TestMethod] public void TakeRandom_ReturnsSpecifiedCount() { // Arrange var items = Enumerable.Range(1, 100).ToList(); // Act var random10 = items.TakeRandom(10).ToList(); // Assert Assert.AreEqual(10, random10.Count); Assert.IsTrue(random10.All(x => items.Contains(x))); } [TestMethod] public void Split_DividesIntoParts() { // Arrange var items = Enumerable.Range(1, 10).ToList(); // Act var parts = items.Split(3).ToList(); // Assert Assert.AreEqual(3, parts.Count); Assert.AreEqual(4, parts[0].Count()); // 10 / 3 = 向上取整 4 Assert.AreEqual(4, parts[1].Count()); Assert.AreEqual(2, parts[2].Count()); } [TestMethod] public void AllUnique_ChecksUniqueness() { // Arrange var uniqueList = new[] { 1, 2, 3, 4 }; var duplicateList = new[] { 1, 2, 2, 3 }; // Act & Assert Assert.IsTrue(uniqueList.AllUnique()); Assert.IsFalse(duplicateList.AllUnique()); } [TestMethod] public void HasDuplicates_DetectsDuplicates() { // Arrange var uniqueList = new[] { 1, 2, 3, 4 }; var duplicateList = new[] { 1, 2, 2, 3 }; // Act & Assert Assert.IsFalse(uniqueList.HasDuplicates()); Assert.IsTrue(duplicateList.HasDuplicates()); } [TestMethod] public void GetDuplicates_ReturnsOnlyDuplicates() { // Arrange var items = new[] { 1, 2, 2, 3, 3, 3, 4 }; // Act var duplicates = items.GetDuplicates().ToList(); // Assert Assert.AreEqual(2, duplicates.Count); CollectionAssert.Contains(duplicates, 2); CollectionAssert.Contains(duplicates, 3); CollectionAssert.DoesNotContain(duplicates, 1); CollectionAssert.DoesNotContain(duplicates, 4); } [TestMethod] public void Interleave_MergesAlternately() { // Arrange var first = new[] { 1, 3, 5 }; var second = new[] { 2, 4, 6 }; // Act var result = first.Interleave(second).ToList(); // Assert CollectionAssert.AreEqual(new[] { 1, 2, 3, 4, 5, 6 }, result); } [TestMethod] public void Interleave_DifferentLengths_MergesCorrectly() { // Arrange var first = new[] { 1, 3 }; var second = new[] { 2, 4, 6, 8 }; // Act var result = first.Interleave(second).ToList(); // Assert CollectionAssert.AreEqual(new[] { 1, 2, 3, 4, 6, 8 }, result); } [TestMethod] public void WhereNotNull_FiltersNullValues() { // Arrange var items = new List { "A", null, "B", null, "C" }; // Act var result = items.WhereNotNull().ToList(); // Assert Assert.AreEqual(3, result.Count); CollectionAssert.AreEqual(new[] { "A", "B", "C" }, result); } [TestMethod] public void ToDictionaryList_GroupsCorrectly() { // Arrange var items = new List { new() { Id = 1, Name = "A", ParentId = 1 }, new() { Id = 2, Name = "B", ParentId = 1 }, new() { Id = 3, Name = "C", ParentId = 2 }, new() { Id = 4, Name = "D", ParentId = 2 } }; // Act var grouped = items.ToDictionaryList( x => x.ParentId!.Value, x => x.Name ); // Assert Assert.AreEqual(2, grouped.Count); Assert.AreEqual(2, grouped[1].Count); CollectionAssert.Contains(grouped[1], "A"); CollectionAssert.Contains(grouped[1], "B"); Assert.AreEqual(2, grouped[2].Count); CollectionAssert.Contains(grouped[2], "C"); CollectionAssert.Contains(grouped[2], "D"); } #endregion #region 实际应用场景测试 [TestMethod] public void Scenario_PaginationWithSearch() { // Arrange: 模拟产品列表搜索和分页 var products = Enumerable.Range(1, 100) .Select(i => new { Id = i, Name = $"Product {i}", Price = i * 10 }) .ToList(); // Act: 搜索价格大于 500 的产品,并分页 var searchResults = products .Where(p => p.Price > 500) .ToPagedResult(1, 10); // Assert Assert.AreEqual(10, searchResults.Items.Count); Assert.IsTrue(searchResults.Items.All(p => p.Price > 500)); Assert.IsTrue(searchResults.TotalCount > 40); // 总共有 50+ 个符合条件的产品 } [TestMethod] public void Scenario_OrganizationTree() { // Arrange: 模拟组织架构树 var departments = new List { new() { Id = 1, Name = "总公司", ParentId = null }, new() { Id = 2, Name = "技术部", ParentId = 1 }, new() { Id = 3, Name = "市场部", ParentId = 1 }, new() { Id = 4, Name = "前端组", ParentId = 2 }, new() { Id = 5, Name = "后端组", ParentId = 2 }, new() { Id = 6, Name = "运营组", ParentId = 3 } }; // Act: 构建组织树 var tree = departments.ToTree(x => x.Id, x => x.ParentId); // Assert: 验证树形结构 Assert.AreEqual(1, tree.Count); // 1 个根节点(总公司) Assert.AreEqual(2, tree[0].Children.Count); // 2 个一级部门 Assert.AreEqual(2, tree[0].Children[0].Children.Count); // 技术部有 2 个组 Assert.AreEqual("前端组", tree[0].Children[0].Children[0].Data.Name); } [TestMethod] public void Scenario_BatchProcessing() { // Arrange: 模拟批量导入数据 var records = Enumerable.Range(1, 1000).ToList(); var processedCount = 0; // Act: 每批处理 100 条记录 records.ForEachBatch(100, batch => { // 模拟批量插入数据库 processedCount += batch.Count(); }); // Assert Assert.AreEqual(1000, processedCount); } [TestMethod] public void Scenario_DataCleaning() { // Arrange: 模拟清洗数据(去除 null、去重、过滤) var rawData = new List { "A", null, "B", "A", null, "C", "B", "D" }; // Act: 数据清洗流水线 var cleanedData = rawData .WhereNotNull() // 去除 null .DistinctBy(x => x) // 去重 .Where(x => x != "D") // 过滤不需要的数据 .ToList(); // Assert Assert.AreEqual(3, cleanedData.Count); CollectionAssert.AreEqual(new[] { "A", "B", "C" }, cleanedData); } #endregion }