using AyCode.Core.Extensions; using AyCode.Core.Tests.TestModels; namespace AyCode.Core.Tests.Serialization; /// /// Tests for Chain API reference preservation with IId objects. /// This is the critical feature for DevExpress DXGrid GridCustomDataSource scenario. /// [TestClass] public class AcBinarySerializerChainReferenceTests { /// /// CRITICAL TEST: DevExpress DXGrid scenario with Chain API. /// Server returns List<Item> for grid display, but we also have internal cache List<Item>. /// When using ThenPopulate, the grid's visible items MUST be the same object references /// from the cache to ensure Blazor binding works correctly. /// [TestMethod] public void ChainPopulate_IIdObjects_PreservesReferences() { // Setup: Create internal cache with 5 categories var internalCache = new List { new() { Id = 1, Name = "Category1", SortOrder = 1 }, new() { Id = 2, Name = "Category2", SortOrder = 2 }, new() { Id = 3, Name = "Category3", SortOrder = 3 }, new() { Id = 4, Name = "Category4", SortOrder = 4 }, new() { Id = 5, Name = "Category5", SortOrder = 5 } }; // Server returns subset of categories (like grid pagination - page 2: items 3-5) var serverData = new List { new() { Id = 3, Name = "Category3_Updated", SortOrder = 33 }, new() { Id = 4, Name = "Category4_Updated", SortOrder = 44 }, new() { Id = 5, Name = "Category5_Updated", SortOrder = 55 } }; // Serialize server response var binary = serverData.ToBinary(); // Grid's visible list (empty initially) var gridVisibleList = new List(); // CRITICAL: Use Chain API to parse once, populate both cache and grid using var chain = binary.BinaryToChain>(); // First: Update internal cache (will become 3 items: 3-5 updated) chain.ThenPopulate(internalCache); // Second: Populate grid's visible list chain.ThenPopulate(gridVisibleList); // VERIFICATION: After ThenPopulate, internalCache contains the 3 items from server Assert.AreEqual(3, gridVisibleList.Count); Assert.AreEqual(3, internalCache.Count, "ThenPopulate replaces list contents with server data"); // CRITICAL ASSERTION: Grid items MUST be same object references as cache items! Assert.AreSame(internalCache[0], gridVisibleList[0], "Grid item MUST be same reference as cache item for Blazor binding!"); Assert.AreSame(internalCache[1], gridVisibleList[1], "Grid item MUST be same reference as cache item for Blazor binding!"); Assert.AreSame(internalCache[2], gridVisibleList[2], "Grid item MUST be same reference as cache item for Blazor binding!"); // Verify data was updated correctly Assert.AreEqual(3, internalCache[0].Id); Assert.AreEqual("Category3_Updated", internalCache[0].Name); Assert.AreEqual(33, internalCache[0].SortOrder); } /// /// Test JSON Chain API reference preservation. /// [TestMethod] public void JsonChainPopulate_IIdObjects_PreservesReferences() { // Setup: Create internal cache var internalCache = new List { new() { Id = 1, Name = "Category1", SortOrder = 1 }, new() { Id = 2, Name = "Category2", SortOrder = 2 }, new() { Id = 3, Name = "Category3", SortOrder = 3 } }; // Server returns subset var serverData = new List { new() { Id = 2, Name = "Category2_Updated", SortOrder = 22 }, new() { Id = 3, Name = "Category3_Updated", SortOrder = 33 } }; // Serialize server response var json = serverData.ToJson(); // Grid's visible list var gridVisibleList = new List(); // Use JSON Chain API using var chain = json.JsonToChain>(); // Update internal cache (will replace with 2 items) chain.ThenPopulate(internalCache); // Populate grid's visible list chain.ThenPopulate(gridVisibleList); // VERIFICATION Assert.AreEqual(2, gridVisibleList.Count); Assert.AreEqual(2, internalCache.Count, "ThenPopulate replaces list contents"); // CRITICAL: Same references! Assert.AreSame(internalCache[0], gridVisibleList[0]); Assert.AreSame(internalCache[1], gridVisibleList[1]); // Verify updates Assert.AreEqual(2, internalCache[0].Id); Assert.AreEqual("Category2_Updated", internalCache[0].Name); Assert.AreEqual(22, internalCache[0].SortOrder); } /// /// Test with Guid-based IId implementation. /// [TestMethod] public void ChainPopulate_GuidIId_PreservesReferences() { var cache = new List { new() { Id = Guid.NewGuid(), Code = "ORD-001", Count = 10 }, new() { Id = Guid.NewGuid(), Code = "ORD-002", Count = 20 } }; var id1 = cache[0].Id; var id2 = cache[1].Id; var serverData = new List { new() { Id = id1, Code = "ORD-001-UPDATED", Count = 11 }, new() { Id = id2, Code = "ORD-002-UPDATED", Count = 22 } }; var binary = serverData.ToBinary(); var gridList = new List(); using var chain = binary.BinaryToChain>(); chain.ThenPopulate(cache); chain.ThenPopulate(gridList); Assert.AreEqual(2, gridList.Count); Assert.AreSame(cache[0], gridList[0], "Guid-based IId should also preserve references"); Assert.AreSame(cache[1], gridList[1]); Assert.AreEqual("ORD-001-UPDATED", cache[0].Code); } /// /// Test multiple chain operations with different subsets. /// [TestMethod] public void ChainPopulate_MultipleSubsets_PreservesReferencesAcrossAll() { // Large internal cache var internalCache = Enumerable.Range(1, 10) .Select(i => new SharedCategory_All_True { Id = i, Name = $"Category{i}", SortOrder = i * 10 }) .ToList(); // Server returns items 3-7 var serverData = Enumerable.Range(3, 5) .Select(i => new SharedCategory_All_True { Id = i, Name = $"Category{i}_Updated", SortOrder = i * 11 }) .ToList(); var binary = serverData.ToBinary(); // Three different grid pages/views var gridPage1 = new List(); var gridPage2 = new List(); var gridPage3 = new List(); using var chain = binary.BinaryToChain>(); // Update cache first chain.ThenPopulate(internalCache); // Populate different grid pages chain.ThenPopulate(gridPage1); chain.ThenPopulate(gridPage2); chain.ThenPopulate(gridPage3); // All pages should have same references Assert.AreEqual(5, gridPage1.Count); Assert.AreEqual(5, gridPage2.Count); Assert.AreEqual(5, gridPage3.Count); // All three pages point to the SAME objects for (int i = 0; i < 5; i++) { Assert.AreSame(gridPage1[i], gridPage2[i], $"Page1 and Page2 item {i} must be same reference"); Assert.AreSame(gridPage2[i], gridPage3[i], $"Page2 and Page3 item {i} must be same reference"); Assert.AreSame(internalCache[i], gridPage1[i], $"Cache and Page1 item {i} must be same reference"); } } /// /// Simple debug test to verify chain reference tracking works. /// [TestMethod] public void ChainPopulate_SimpleCase_Works() { var list1 = new List(); var list2 = new List(); var serverData = new List { new() { Id = 1, Name = "Cat1", SortOrder = 10 } }; var binary = serverData.ToBinary(); using var chain = binary.BinaryToChain>(); // First populate chain.ThenPopulate(list1); Assert.AreEqual(1, list1.Count); Assert.AreEqual(1, list1[0].Id); // Second populate - should reuse same object chain.ThenPopulate(list2); Assert.AreEqual(1, list2.Count); Assert.AreSame(list1[0], list2[0], "Should be same object reference!"); } }