AyCode.Blazor/AyCode.Blazor.Components.Tests/Grids/TestMgGrid.cs

203 lines
7.4 KiB
C#

using AyCode.Blazor.Components.Components.Grids;
using AyCode.Core.Helpers;
using AyCode.Core.Interfaces;
using AyCode.Core.Loggers;
using AyCode.Core.Tests.TestModels;
using AyCode.Services.Server.SignalRs;
using AyCode.Services.Server.Tests.SignalRs;
using AyCode.Services.Server.Tests.SignalRs.SignalRDatasources;
using AyCode.Services.SignalRs;
using DevExpress.Blazor;
using Microsoft.AspNetCore.Components;
using System.Reflection;
using Microsoft.AspNetCore.Components.Rendering;
namespace AyCode.Blazor.Components.Tests.Grids;
/// <summary>
/// Test DataSource that extends TestOrderItemObservableDataSource with the 3-parameter constructor
/// required by MgGridBase.OnInitializedAsync which uses Activator.CreateInstance.
/// </summary>
public class TestGridOrderItemDataSource : TestOrderItemObservableDataSource
{
public TestGridOrderItemDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags crudTags)
: base(signalRClient, crudTags) { }
public TestGridOrderItemDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags crudTags, params object[]? contextIds)
: base(signalRClient, crudTags)
{
}
}
/// <summary>
/// Event args for dynamic column adding event.
/// Provides a delegate to add custom attributes to the column.
/// </summary>
public class DynamicColumnAddingEventArgs
{
public required string FieldName { get; init; }
public required PropertyInfo PropertyInfo { get; init; }
/// <summary>
/// Dictionary of additional attributes to add to the column.
/// Key is the attribute name, value is the attribute value.
/// </summary>
public Dictionary<string, object?> AdditionalAttributes { get; } = new();
}
/// <summary>
/// Base test implementation of MgGridBase for testing grid functionality.
/// Overrides layout persistence to use in-memory storage for testing.
/// Automatically builds columns from TDataItem properties using reflection.
/// </summary>
public abstract class TestMgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>
: MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient>
where TSignalRDataSource : AcSignalRDataSource<TDataItem, TId, AcObservableCollection<TDataItem>>
where TDataItem : class, IId<TId>
where TId : struct
where TLoggerClient : AcLoggerBase
{
private int _testUserId;
private bool _columnsInitialized;
/// <summary>
/// In-memory storage for layout persistence testing.
/// Shared across all instances to simulate localStorage behavior.
/// </summary>
public static Dictionary<string, GridPersistentLayout?> LayoutStorage { get; } = new();
/// <summary>
/// Indicates whether data source has been loaded
/// </summary>
public bool IsDataSourceLoaded { get; private set; }
/// <summary>
/// Event called when a dynamic column is being added. Allows customization of column properties.
/// Add attributes to eventArgs.AdditionalAttributes dictionary.
/// </summary>
[Parameter]
public Action<DynamicColumnAddingEventArgs>? OnDynamicColumnAttributeAdding { get; set; }
public void SetTestUserId(int userId) => _testUserId = userId;
public int GetTestUserId() => _testUserId;
protected override int GetLayoutUserId() => _testUserId;
public static void ClearLayoutStorage() => LayoutStorage.Clear();
protected override Task<GridPersistentLayout?> LoadLayoutFromLocalStorageAsync(string localStorageKey)
{
LayoutStorage.TryGetValue(localStorageKey, out var layout);
return Task.FromResult(layout);
}
protected override Task SaveLayoutToLocalStorageAsync(GridPersistentLayout layout, string localStorageKey)
{
LayoutStorage[localStorageKey] = layout;
return Task.CompletedTask;
}
/// <summary>
/// Waits for the data source to be loaded using TaskHelper
/// </summary>
public Task<bool> WaitForDataSourceLoadedAsync(int timeoutMs = 5000)
{
return TaskHelper.WaitToAsync(() => IsDataSourceLoaded, timeoutMs);
}
protected override void OnParametersSet()
{
if (!_columnsInitialized)
{
// Build columns from TDataItem properties using reflection
Columns = BuildColumnsFromDataItem;
_columnsInitialized = true;
}
base.OnParametersSet();
// Subscribe to OnDataSourceChanged to know when data is loaded
OnDataSourceChanged = EventCallback.Factory.Create<IList<TDataItem>>(this, _ =>
{
IsDataSourceLoaded = true;
});
}
/// <summary>
/// Builds grid columns from TDataItem properties using reflection
/// </summary>
private void BuildColumnsFromDataItem(RenderTreeBuilder builder)
{
var properties = typeof(TDataItem).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead && IsSimpleType(p.PropertyType))
.ToList();
var seq = 0;
foreach (var property in properties)
{
// Create event args and invoke the event
var eventArgs = new DynamicColumnAddingEventArgs
{
FieldName = property.Name,
PropertyInfo = property
};
OnDynamicColumnAttributeAdding?.Invoke(eventArgs);
builder.OpenComponent<MgGridDataColumn>(seq++);
builder.AddAttribute(seq++, nameof(MgGridDataColumn.Name), property.Name);
builder.AddAttribute(seq++, nameof(MgGridDataColumn.FieldName), property.Name);
builder.AddAttribute(seq++, nameof(MgGridDataColumn.Width), GetDefaultWidth(property.PropertyType));
// Add additional attributes from the event
foreach (var attr in eventArgs.AdditionalAttributes)
{
builder.AddAttribute(seq++, attr.Key, attr.Value);
}
builder.CloseComponent();
}
}
/// <summary>
/// Determines if a type is a simple type suitable for grid display
/// </summary>
private static bool IsSimpleType(Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
return underlyingType.IsPrimitive
|| underlyingType == typeof(string)
|| underlyingType == typeof(decimal)
|| underlyingType == typeof(DateTime)
|| underlyingType == typeof(DateTimeOffset)
|| underlyingType == typeof(TimeSpan)
|| underlyingType == typeof(Guid)
|| underlyingType.IsEnum;
}
/// <summary>
/// Gets default column width based on property type
/// </summary>
private static string GetDefaultWidth(Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
return underlyingType switch
{
_ when underlyingType == typeof(int) || underlyingType == typeof(long) => "80px",
_ when underlyingType == typeof(decimal) || underlyingType == typeof(double) || underlyingType == typeof(float) => "100px",
_ when underlyingType == typeof(bool) => "60px",
_ when underlyingType == typeof(DateTime) || underlyingType == typeof(DateTimeOffset) => "150px",
_ when underlyingType == typeof(string) => "200px",
_ when underlyingType == typeof(Guid) => "250px",
_ => "120px"
};
}
}
/// <summary>
/// Test grid for TestOrderItem entities
/// </summary>
public class TestMgGridOrderItem : TestMgGridBase<TestGridOrderItemDataSource, TestOrderItem, int, TestLogger>
{
}