234 lines
8.7 KiB
C#
234 lines
8.7 KiB
C#
using AyCode.Core.Extensions;
|
|
using AyCode.Core.Tests.TestModels;
|
|
|
|
namespace AyCode.Core.Tests.Serialization;
|
|
|
|
/// <summary>
|
|
/// Tests for Chain API reference preservation with IId objects.
|
|
/// This is the critical feature for DevExpress DXGrid GridCustomDataSource scenario.
|
|
/// </summary>
|
|
[TestClass]
|
|
public class AcBinarySerializerChainReferenceTests
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void ChainPopulate_IIdObjects_PreservesReferences()
|
|
{
|
|
// Setup: Create internal cache with 5 categories
|
|
var internalCache = new List<SharedCategory_All_True>
|
|
{
|
|
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<SharedCategory_All_True>
|
|
{
|
|
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<SharedCategory_All_True>();
|
|
|
|
// CRITICAL: Use Chain API to parse once, populate both cache and grid
|
|
using var chain = binary.BinaryToChain<List<SharedCategory_All_True>>();
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test JSON Chain API reference preservation.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void JsonChainPopulate_IIdObjects_PreservesReferences()
|
|
{
|
|
// Setup: Create internal cache
|
|
var internalCache = new List<SharedCategory_All_True>
|
|
{
|
|
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<SharedCategory_All_True>
|
|
{
|
|
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<SharedCategory_All_True>();
|
|
|
|
// Use JSON Chain API
|
|
using var chain = json.JsonToChain<List<SharedCategory_All_True>>();
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test with Guid-based IId implementation.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void ChainPopulate_GuidIId_PreservesReferences()
|
|
{
|
|
var cache = new List<TestGuidOrder>
|
|
{
|
|
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<TestGuidOrder>
|
|
{
|
|
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<TestGuidOrder>();
|
|
|
|
using var chain = binary.BinaryToChain<List<TestGuidOrder>>();
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test multiple chain operations with different subsets.
|
|
/// </summary>
|
|
[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<SharedCategory_All_True>();
|
|
var gridPage2 = new List<SharedCategory_All_True>();
|
|
var gridPage3 = new List<SharedCategory_All_True>();
|
|
|
|
using var chain = binary.BinaryToChain<List<SharedCategory_All_True>>();
|
|
|
|
// 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");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simple debug test to verify chain reference tracking works.
|
|
/// </summary>
|
|
[TestMethod]
|
|
public void ChainPopulate_SimpleCase_Works()
|
|
{
|
|
var list1 = new List<SharedCategory_All_True>();
|
|
var list2 = new List<SharedCategory_All_True>();
|
|
|
|
var serverData = new List<SharedCategory_All_True>
|
|
{
|
|
new() { Id = 1, Name = "Cat1", SortOrder = 10 }
|
|
};
|
|
|
|
var binary = serverData.ToBinary();
|
|
|
|
using var chain = binary.BinaryToChain<List<SharedCategory_All_True>>();
|
|
|
|
// 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!");
|
|
}
|
|
}
|