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 { }