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;
///
/// Test DataSource that extends TestOrderItemObservableDataSource with the 3-parameter constructor
/// required by MgGridBase.OnInitializedAsync which uses Activator.CreateInstance.
///
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)
{
}
}
///
/// Event args for dynamic column adding event.
/// Provides a delegate to add custom attributes to the column.
///
public class DynamicColumnAddingEventArgs
{
public required string FieldName { get; init; }
public required PropertyInfo PropertyInfo { get; init; }
///
/// Dictionary of additional attributes to add to the column.
/// Key is the attribute name, value is the attribute value.
///
public Dictionary AdditionalAttributes { get; } = new();
}
///
/// 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.
///
public abstract class TestMgGridBase
: MgGridBase
where TSignalRDataSource : AcSignalRDataSource>
where TDataItem : class, IId
where TId : struct
where TLoggerClient : AcLoggerBase
{
private int _testUserId;
private bool _columnsInitialized;
///
/// In-memory storage for layout persistence testing.
/// Shared across all instances to simulate localStorage behavior.
///
public static Dictionary LayoutStorage { get; } = new();
///
/// Indicates whether data source has been loaded
///
public bool IsDataSourceLoaded { get; private set; }
///
/// Event called when a dynamic column is being added. Allows customization of column properties.
/// Add attributes to eventArgs.AdditionalAttributes dictionary.
///
[Parameter]
public Action? 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 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;
}
///
/// Waits for the data source to be loaded using TaskHelper
///
public Task 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>(this, _ =>
{
IsDataSourceLoaded = true;
});
}
///
/// Builds grid columns from TDataItem properties using reflection
///
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(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();
}
}
///
/// Determines if a type is a simple type suitable for grid display
///
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;
}
///
/// Gets default column width based on property type
///
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"
};
}
}
///
/// Test grid for TestOrderItem entities
///
public class TestMgGridOrderItem : TestMgGridBase
{
}