From d927a5b53e8ca4a9cbfe00fbdd741f5e62181bdc Mon Sep 17 00:00:00 2001 From: Wiaan Duvenhage Date: Thu, 21 May 2026 10:13:10 +0200 Subject: [PATCH 1/3] feat: add EnumMultiSelectHandler for filtering collections of enum values --- Directory.Packages.props | 12 +- README.md | 15 +- src/Hotchocolate.MudDataGrid/README.md | 26 +- .../DataTypePropertyBuilderExtensions.cs | 65 +++++ .../DefaultEnumMultiSelectHandler.cs | 60 +++++ .../MudDataGridAdapterTests.cs | 71 +++++ .../MuiDataGridEnumMultiSelectTests.cs | 251 ++++++++++++++++++ 7 files changed, 492 insertions(+), 8 deletions(-) create mode 100644 src/Hotchocolate.MuiDataGrid/DefaultEnumMultiSelectHandler.cs create mode 100644 tests/Hotchocolate.MuiDataGrid.Test/MuiDataGridEnumMultiSelectTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 511cf4e..bd03989 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,12 +12,12 @@ - - - - - - + + + + + + diff --git a/README.md b/README.md index a4355fe..24609fa 100644 --- a/README.md +++ b/README.md @@ -244,4 +244,17 @@ Not all MUI operators are supported for all default handlers. getGridSingleSelectOperators().filter(({ value }) => ['equals', 'isEmpty', 'isNotEmpty', 'isAnyOf'].includes(value) ); -``` \ No newline at end of file +``` + +### Enum collection (`DefaultEnumMultiSelectHandler`) + +`isAnyOf` — matches rows where the entity's enum collection contains **any** of the filter values. + +Register explicitly on the property; auto-detection is not performed: + +```csharp +// Works for IList, ICollection, IEnumerable, and List +builder.Property(u => u.Roles).SetEnumMultiSelectHandler(); +``` + +Filter values are enum name strings normalised via Humanizer, e.g. `SUPER_USER`, `Super User`, and `SuperUser` all resolve to the same member. Unknown values throw `ArgumentException` immediately. diff --git a/src/Hotchocolate.MudDataGrid/README.md b/src/Hotchocolate.MudDataGrid/README.md index fe52740..db7e57e 100644 --- a/src/Hotchocolate.MudDataGrid/README.md +++ b/src/Hotchocolate.MudDataGrid/README.md @@ -105,7 +105,31 @@ Operators are matched **case-insensitively** after trimming. The table below sho | `is not` / `not` | `not` | DateTime, SingleSelect | | `empty` | `isEmpty` | String, Number, DateTime, Guid | | `not empty` | `isNotEmpty` | String, Number, DateTime, Guid | -| `any of` / `is any of` | `isAnyOf` | String, Number, Guid, SingleSelect | +| `any of` / `is any of` | `isAnyOf` | String, Number, Guid, SingleSelect, **EnumMultiSelect** | + +### Enum collection filtering (isAnyOf) + +Use `SetEnumMultiSelectHandler` to filter entity properties that hold a collection of enum values (e.g. `IList`, `ICollection`). A row is returned when its collection contains **at least one** of the selected values. + +**Data type configuration:** + +```csharp +builder.Property(u => u.Roles).SetEnumMultiSelectHandler(); +``` + +**Mud / MUI filter payload:** + +```graphql +filterDefinitions: [{ + field: "roles", + operator: "any of", // or "is any of" + value: ["ADMIN", "SUPER_USER"] +}] +``` + +Enum string values are normalised with [Humanizer](https://github.com/Humanizr/Humanizer) before parsing, so `SUPER_USER`, `Super User`, and `SuperUser` all resolve to the same enum member. Unknown enum strings throw an `ArgumentException` immediately (fail-fast, consistent with `DefaultEnumSingleSelectHandler`). + +`SetEnumMultiSelectHandler` is available for: `IEnumerable`, `ICollection`, `IList`, and `List`. It **does not auto-register** — you must call it explicitly on the relevant property. Any operator not listed above throws a `GraphQLException` immediately (fail-fast). diff --git a/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs b/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs index 9c68364..67b1c39 100644 --- a/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs +++ b/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs @@ -11,6 +11,71 @@ public static DataTypePropertyBuilder SetEnumHandler( return builder.SetHandler(new DefaultEnumSingleSelectHandler()); } + /// + /// Registers for an + /// property. Only the isAnyOf operator + /// is supported: rows are returned when the collection contains any of the + /// selected enum values. + /// + /// The entity type. + /// The enum type stored in the collection. + /// The property builder to configure. + /// The same builder instance for chaining. + public static DataTypePropertyBuilder> SetEnumMultiSelectHandler( + this DataTypePropertyBuilder> builder) + where TEnum : struct, Enum + { + return builder.SetHandler(new DefaultEnumMultiSelectHandler()); + } + + /// + /// Registers for an + /// property. Only the isAnyOf operator + /// is supported. + /// + /// The entity type. + /// The enum type stored in the collection. + /// The property builder to configure. + /// The same builder instance for chaining. + public static DataTypePropertyBuilder> SetEnumMultiSelectHandler( + this DataTypePropertyBuilder> builder) + where TEnum : struct, Enum + { + return builder.SetHandler(new DefaultEnumMultiSelectHandler()); + } + + /// + /// Registers for an + /// property. Only the isAnyOf operator is + /// supported. + /// + /// The entity type. + /// The enum type stored in the collection. + /// The property builder to configure. + /// The same builder instance for chaining. + public static DataTypePropertyBuilder> SetEnumMultiSelectHandler( + this DataTypePropertyBuilder> builder) + where TEnum : struct, Enum + { + return builder.SetHandler(new DefaultEnumMultiSelectHandler()); + } + + /// + /// Registers for a + /// property. Only the isAnyOf operator is + /// supported. + /// + /// The entity type. + /// The enum type stored in the collection. + /// The property builder to configure. + /// The same builder instance for chaining. + public static DataTypePropertyBuilder> SetEnumMultiSelectHandler( + this DataTypePropertyBuilder> builder) + where TEnum : struct, Enum + { + return builder.SetHandler(new DefaultEnumMultiSelectHandler()); + } + public static DataTypePropertyBuilder SetNodeIdHandler( this DataTypePropertyBuilder builder, INodeIdSerializer idSerializer, diff --git a/src/Hotchocolate.MuiDataGrid/DefaultEnumMultiSelectHandler.cs b/src/Hotchocolate.MuiDataGrid/DefaultEnumMultiSelectHandler.cs new file mode 100644 index 0000000..e40d7b3 --- /dev/null +++ b/src/Hotchocolate.MuiDataGrid/DefaultEnumMultiSelectHandler.cs @@ -0,0 +1,60 @@ +namespace Stackworx.Hotchocolate.MuiDataGrid; + +using Humanizer; + +/// +/// Handles filtering for entity properties that are collections of enum values. +/// Only the isAnyOf operator is supported: a row matches when the entity's +/// enum collection contains at least one of the selected filter values. +/// +/// The entity type. +/// The enum type stored in the collection. +public class DefaultEnumMultiSelectHandler : ExpressionBuilderHandler + where TEnum : struct, Enum +{ + protected override Expression InternalHandle(ColumnLookupMember member, ExpressionBuilderFlavour flavour, MuiDataGridFilterItemInput filter) + { + return filter.Operator switch + { + "isAnyOf" => this.BuildIsAnyOf(member, filter), + _ => throw new ArgumentException($"Unknown operator: {filter.Operator}"), + }; + } + + protected override dynamic ParseValue(ColumnLookupMember member, MuiValue value) + { + var v = value.AsString(); + v = v.Humanize(LetterCasing.Title).Transform(To.LowerCase, To.TitleCase).Dehumanize(); + if (Enum.TryParse(v, out var result)) + { + return result; + } + + throw new ArgumentException($"Failed to Parse {typeof(TEnum).Name}: {v}"); + } + + // Builds: entity.Collection.Any(e => filterValues.Contains(e)) + private Expression BuildIsAnyOf(ColumnLookupMember member, MuiDataGridFilterItemInput filter) + { + filter.Value.AssertNotNull(filter.Operator); + + var enumValues = filter.Value + .AsArray() + .Select(v => (TEnum)this.ParseValue(member, v)) + .ToList(); + + var filterConstant = Expression.Constant(enumValues, typeof(List)); + + var enumParam = Expression.Parameter(typeof(TEnum), "e"); + var containsMethod = typeof(List).GetMethod("Contains", new[] { typeof(TEnum) })!; + var containsCall = Expression.Call(filterConstant, containsMethod, enumParam); + var predicate = Expression.Lambda>(containsCall, enumParam); + + var anyMethod = typeof(Enumerable) + .GetMethods() + .First(m => m.Name == "Any" && m.GetParameters().Length == 2) + .MakeGenericMethod(typeof(TEnum)); + + return Expression.Call(anyMethod, member.Expression, predicate); + } +} \ No newline at end of file diff --git a/tests/Hotchocolate.MuiDataGrid.Test/MudDataGridAdapterTests.cs b/tests/Hotchocolate.MuiDataGrid.Test/MudDataGridAdapterTests.cs index cae5292..d130709 100644 --- a/tests/Hotchocolate.MuiDataGrid.Test/MudDataGridAdapterTests.cs +++ b/tests/Hotchocolate.MuiDataGrid.Test/MudDataGridAdapterTests.cs @@ -58,6 +58,77 @@ public void Map_ShouldPreserveSortDefinitionOrder() result.Sorting!.Select(x => x.Field).Should().Equal("first", "second", "third"); } + [Fact] + public void Map_ShouldMapEnumListWithAnyOf_ToIsAnyOf() + { + // Verifies that a MudDataGrid "any of" filter carrying a list of enum string + // values is forwarded untouched as an isAnyOf MUI filter item. + var enumValues = new MuiValue(new List { "ADMIN", "SUPER_USER" }); + + var input = new MudDataGridFilterInput + { + FilterDefinitions = + [ + new MudDataGridFilterDefinitionInput("roles", "any of", enumValues), + ], + }; + + var result = MudToMuiDataGridAdapter.Map(input); + + result.Filters.Should().NotBeNull(); + result.Filters!.Items.Should().HaveCount(1); + + var item = result.Filters.Items[0]; + item.Field.Should().Be("roles"); + item.Operator.Should().Be("isAnyOf"); + item.Value.Should().Be(enumValues); + item.FromInput.Should().Be("mud"); + } + + [Fact] + public void Map_ShouldMapEnumListWithIsAnyOf_ToIsAnyOf() + { + var enumValues = new MuiValue(new List { "READ_ONLY" }); + + var input = new MudDataGridFilterInput + { + FilterDefinitions = + [ + new MudDataGridFilterDefinitionInput("roles", "is any of", enumValues), + ], + }; + + var result = MudToMuiDataGridAdapter.Map(input); + + result.Filters!.Items.Single().Operator.Should().Be("isAnyOf"); + result.Filters.Items.Single().Value.Should().Be(enumValues); + } + + [Fact] + public void Map_ShouldPreserveEnumArrayValuesAcrossMapping() + { + // Ensures the MuiValue array holding the enum strings is identical by ref + // so no data is lost during operator normalisation. + var enumValues = new MuiValue(new List { "ADMIN", "SUPER_USER", "READ_ONLY" }); + + var input = new MudDataGridFilterInput + { + FilterDefinitions = + [ + new MudDataGridFilterDefinitionInput("permissions", "any of", enumValues), + ], + }; + + var result = MudToMuiDataGridAdapter.Map(input); + + var mappedItem = result.Filters!.Items.Single(); + mappedItem.Value.Should().BeSameAs(enumValues, "the original MuiValue array must not be cloned or altered"); + + // Verify the string values are accessible and intact + var arrayValues = mappedItem.Value!.AsArray().Select(v => v.AsString()).ToList(); + arrayValues.Should().Equal("ADMIN", "SUPER_USER", "READ_ONLY"); + } + [Fact] public void Map_ShouldFailFastWithGraphqlError_WhenOperatorIsUnsupported() { diff --git a/tests/Hotchocolate.MuiDataGrid.Test/MuiDataGridEnumMultiSelectTests.cs b/tests/Hotchocolate.MuiDataGrid.Test/MuiDataGridEnumMultiSelectTests.cs new file mode 100644 index 0000000..c59f38a --- /dev/null +++ b/tests/Hotchocolate.MuiDataGrid.Test/MuiDataGridEnumMultiSelectTests.cs @@ -0,0 +1,251 @@ +namespace Stackworx.Hotchocolate.MuiDataGrid; + +using FluentAssertions; + +/// +/// Tests for which handles +/// filtering on entity properties that are collections of enum values. +/// +public class MuiDataGridEnumMultiSelectTests +{ + private enum Role + { + Admin, + SuperUser, + ReadOnly, + Viewer, + } + + private enum Permission + { + Read, + Write, + } + + private static List TestUsers => + [ + new() { Id = 1, Name = "Alice", Roles = [Role.Admin] }, + new() { Id = 2, Name = "Bob", Roles = [Role.SuperUser] }, + new() { Id = 3, Name = "Carol", Roles = [Role.Admin, Role.SuperUser] }, + new() { Id = 4, Name = "Dave", Roles = [Role.ReadOnly] }, + new() { Id = 5, Name = "Eve", Roles = [] }, + ]; + + [Fact] + public void IsAnyOf_SingleFilter_ReturnsUsersWithMatchingRole() + { + var dataType = new UserWithRolesDataType(); + var filter = new MuiDataGridFilterInput + { + Items = + [ + new( + Field: "roles", + Value: new MuiValue(new List { "ADMIN" }), + Operator: "isAnyOf"), + ], + }; + + var result = TestUsers.AsQueryable().Where(dataType.Filter(filter)).ToList(); + + result.Should().HaveCount(2); + result.Select(u => u.Name).Should().BeEquivalentTo("Alice", "Carol"); + } + + [Fact] + public void IsAnyOf_MultipleFilterValues_ReturnsUsersWithAnyMatchingRole() + { + var dataType = new UserWithRolesDataType(); + var filter = new MuiDataGridFilterInput + { + Items = + [ + new( + Field: "roles", + Value: new MuiValue(new List { "ADMIN", "SUPER_USER" }), + Operator: "isAnyOf"), + ], + }; + + var result = TestUsers.AsQueryable().Where(dataType.Filter(filter)).ToList(); + + // Alice (Admin), Bob (SuperUser), Carol (Admin + SuperUser) — Dave (ReadOnly) and Eve (empty) excluded + result.Should().HaveCount(3); + result.Select(u => u.Name).Should().BeEquivalentTo("Alice", "Bob", "Carol"); + } + + [Fact] + public void IsAnyOf_NoOverlap_ReturnsEmptyList() + { + var dataType = new UserWithRolesDataType(); + var filter = new MuiDataGridFilterInput + { + Items = + [ + new( + Field: "roles", + Value: new MuiValue(new List { "VIEWER" }), + Operator: "isAnyOf"), + ], + }; + + var result = TestUsers.AsQueryable().Where(dataType.Filter(filter)).ToList(); + + result.Should().BeEmpty(); + } + + [Fact] + public void IsAnyOf_AllRolesInFilter_ReturnsAllUsersWithAtLeastOneRole() + { + var dataType = new UserWithRolesDataType(); + var filter = new MuiDataGridFilterInput + { + Items = + [ + new( + Field: "roles", + Value: new MuiValue(new List { "ADMIN", "SUPER_USER", "READ_ONLY", "VIEWER" }), + Operator: "isAnyOf"), + ], + }; + + var result = TestUsers.AsQueryable().Where(dataType.Filter(filter)).ToList(); + + // Eve has no roles and should be excluded + result.Should().HaveCount(4); + result.Select(u => u.Name).Should().BeEquivalentTo("Alice", "Bob", "Carol", "Dave"); + } + + [Fact] + public void IsAnyOf_UserWithMultipleRoles_OnlyMatchesWhenFilterRolePresent() + { + var dataType = new UserWithRolesDataType(); + var filter = new MuiDataGridFilterInput + { + Items = + [ + new( + Field: "roles", + Value: new MuiValue(new List { "READ_ONLY" }), + Operator: "isAnyOf"), + ], + }; + + var result = TestUsers.AsQueryable().Where(dataType.Filter(filter)).ToList(); + + result.Should().HaveCount(1); + result.Single().Name.Should().Be("Dave"); + } + + [Theory] + [InlineData("ADMIN")] + [InlineData("Admin")] + [InlineData("admin")] + public void IsAnyOf_EnumNameCaseVariants_ParsedCorrectly(string enumName) + { + var dataType = new UserWithRolesDataType(); + var filter = new MuiDataGridFilterInput + { + Items = + [ + new( + Field: "roles", + Value: new MuiValue(new List { enumName }), + Operator: "isAnyOf"), + ], + }; + + var result = TestUsers.AsQueryable().Where(dataType.Filter(filter)).ToList(); + + result.Should().HaveCount(2, $"'{enumName}' should resolve to Role.Admin"); + result.Select(u => u.Name).Should().BeEquivalentTo("Alice", "Carol"); + } + + [Fact] + public void IsAnyOf_UnknownEnumValue_ThrowsArgumentException() + { + var dataType = new UserWithRolesDataType(); + var filter = new MuiDataGridFilterInput + { + Items = + [ + new( + Field: "roles", + Value: new MuiValue(new List { "GOD_MODE" }), + Operator: "isAnyOf"), + ], + }; + + var act = () => TestUsers.AsQueryable().Where(dataType.Filter(filter)).ToList(); + + act.Should().Throw() + .WithMessage("*Failed to Parse*"); + } + + [Fact] + public void UnsupportedOperator_ThrowsArgumentException() + { + var dataType = new UserWithRolesDataType(); + var filter = new MuiDataGridFilterInput + { + Items = + [ + new( + Field: "roles", + Value: new MuiValue(new List { "ADMIN" }), + Operator: "is"), // only isAnyOf is supported + ], + }; + + var act = () => TestUsers.AsQueryable().Where(dataType.Filter(filter)).ToList(); + + act.Should().Throw() + .WithMessage("*Unknown operator*"); + } + + [Fact] + public void AllCollectionInterfaceOverloads_RegisterWithoutError() + { + // Ensures all four overloads compile and register without exceptions at runtime. + var act = () => new TestAllInterfacesDataType(); + + act.Should().NotThrow(); + } + + private sealed class UserWithRolesDataType : DataType + { + protected override void Configure(DataTypeBuilder builder) + { + builder.Property(u => u.Name); + builder.Property(u => u.Roles).SetEnumMultiSelectHandler(); + } + } + + private sealed class TestAllInterfacesDataType : DataType + { + protected override void Configure(DataTypeBuilder builder) + { + builder.Property(r => r.RequiredPermissions).SetEnumMultiSelectHandler(); // ICollection + builder.Property(r => r.GrantedPermissions).SetEnumMultiSelectHandler(); // IEnumerable + builder.Property(r => r.DeniedPermissions).SetEnumMultiSelectHandler(); // List + } + } + + private class UserWithRoles + { + public int Id { get; set; } + + public string Name { get; set; } = string.Empty; + + public IList Roles { get; set; } = new List(); + } + + private class ResourceWithPermissions + { + public ICollection RequiredPermissions { get; set; } = new List(); + + public IEnumerable GrantedPermissions { get; set; } = new List(); + + public List DeniedPermissions { get; set; } = []; + } +} \ No newline at end of file From 7593bc53fdefad270d1399a2b556ecbd09679829 Mon Sep 17 00:00:00 2001 From: Wiaan Duvenhage Date: Thu, 21 May 2026 13:02:34 +0200 Subject: [PATCH 2/3] fix: remove IList, ICollection, List handlers --- .../DataTypePropertyBuilderExtensions.cs | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs b/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs index 67b1c39..900192f 100644 --- a/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs +++ b/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs @@ -28,54 +28,6 @@ public static DataTypePropertyBuilder> SetEnumMultiSelectH return builder.SetHandler(new DefaultEnumMultiSelectHandler()); } - /// - /// Registers for an - /// property. Only the isAnyOf operator - /// is supported. - /// - /// The entity type. - /// The enum type stored in the collection. - /// The property builder to configure. - /// The same builder instance for chaining. - public static DataTypePropertyBuilder> SetEnumMultiSelectHandler( - this DataTypePropertyBuilder> builder) - where TEnum : struct, Enum - { - return builder.SetHandler(new DefaultEnumMultiSelectHandler()); - } - - /// - /// Registers for an - /// property. Only the isAnyOf operator is - /// supported. - /// - /// The entity type. - /// The enum type stored in the collection. - /// The property builder to configure. - /// The same builder instance for chaining. - public static DataTypePropertyBuilder> SetEnumMultiSelectHandler( - this DataTypePropertyBuilder> builder) - where TEnum : struct, Enum - { - return builder.SetHandler(new DefaultEnumMultiSelectHandler()); - } - - /// - /// Registers for a - /// property. Only the isAnyOf operator is - /// supported. - /// - /// The entity type. - /// The enum type stored in the collection. - /// The property builder to configure. - /// The same builder instance for chaining. - public static DataTypePropertyBuilder> SetEnumMultiSelectHandler( - this DataTypePropertyBuilder> builder) - where TEnum : struct, Enum - { - return builder.SetHandler(new DefaultEnumMultiSelectHandler()); - } - public static DataTypePropertyBuilder SetNodeIdHandler( this DataTypePropertyBuilder builder, INodeIdSerializer idSerializer, From 5dfff24e4a9ad4a4c4988332a922f21bcae4ef00 Mon Sep 17 00:00:00 2001 From: Wiaan Duvenhage Date: Thu, 21 May 2026 13:17:33 +0200 Subject: [PATCH 3/3] feat: add overloads for SetEnumMultiSelectHandler to support ICollection and IList --- README.md | 2 +- src/Hotchocolate.MudDataGrid/README.md | 4 +- .../DataTypePropertyBuilderExtensions.cs | 57 ++++++++++++++++++- .../MuiDataGridEnumMultiSelectTests.cs | 8 ++- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 24609fa..8ea135c 100644 --- a/README.md +++ b/README.md @@ -253,7 +253,7 @@ getGridSingleSelectOperators().filter(({ value }) => Register explicitly on the property; auto-detection is not performed: ```csharp -// Works for IList, ICollection, IEnumerable, and List +// Configure once for IEnumerable; this also covers IList and ICollection builder.Property(u => u.Roles).SetEnumMultiSelectHandler(); ``` diff --git a/src/Hotchocolate.MudDataGrid/README.md b/src/Hotchocolate.MudDataGrid/README.md index db7e57e..ca00c51 100644 --- a/src/Hotchocolate.MudDataGrid/README.md +++ b/src/Hotchocolate.MudDataGrid/README.md @@ -109,7 +109,7 @@ Operators are matched **case-insensitively** after trimming. The table below sho ### Enum collection filtering (isAnyOf) -Use `SetEnumMultiSelectHandler` to filter entity properties that hold a collection of enum values (e.g. `IList`, `ICollection`). A row is returned when its collection contains **at least one** of the selected values. +Use `SetEnumMultiSelectHandler` to filter entity properties that hold a collection of enum values. Configure it for `IEnumerable` and it will also apply to `IList` and `ICollection` because they implement `IEnumerable`. A row is returned when its collection contains **at least one** of the selected values. **Data type configuration:** @@ -129,7 +129,7 @@ filterDefinitions: [{ Enum string values are normalised with [Humanizer](https://github.com/Humanizr/Humanizer) before parsing, so `SUPER_USER`, `Super User`, and `SuperUser` all resolve to the same enum member. Unknown enum strings throw an `ArgumentException` immediately (fail-fast, consistent with `DefaultEnumSingleSelectHandler`). -`SetEnumMultiSelectHandler` is available for: `IEnumerable`, `ICollection`, `IList`, and `List`. It **does not auto-register** — you must call it explicitly on the relevant property. +`SetEnumMultiSelectHandler` is defined for `IEnumerable`. This also covers `IList`, `ICollection`, and `List` implementations. It **does not auto-register** — you must call it explicitly on the relevant property. Any operator not listed above throws a `GraphQLException` immediately (fail-fast). diff --git a/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs b/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs index 900192f..8d51ee9 100644 --- a/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs +++ b/src/Hotchocolate.MuiDataGrid/DataTypePropertyBuilderExtensions.cs @@ -13,9 +13,9 @@ public static DataTypePropertyBuilder SetEnumHandler( /// /// Registers for an - /// property. Only the isAnyOf operator - /// is supported: rows are returned when the collection contains any of the - /// selected enum values. + /// property. Only the isAnyOf + /// operator is supported: rows are returned when the collection contains any + /// of the selected enum values. /// /// The entity type. /// The enum type stored in the collection. @@ -28,6 +28,57 @@ public static DataTypePropertyBuilder> SetEnumMultiSelectH return builder.SetHandler(new DefaultEnumMultiSelectHandler()); } + /// + /// Registers for an + /// property. Only the isAnyOf + /// operator is supported: rows are returned when the collection contains any + /// of the selected enum values. + /// + /// The entity type. + /// The enum type stored in the collection. + /// The property builder to configure. + /// The same builder instance for chaining. + public static DataTypePropertyBuilder> SetEnumMultiSelectHandler( + this DataTypePropertyBuilder> builder) + where TEnum : struct, Enum + { + return builder.SetHandler(new DefaultEnumMultiSelectHandler()); + } + + /// + /// Registers for an + /// property. Only the isAnyOf + /// operator is supported: rows are returned when the collection contains any + /// of the selected enum values. + /// + /// The entity type. + /// The enum type stored in the collection. + /// The property builder to configure. + /// The same builder instance for chaining. + public static DataTypePropertyBuilder> SetEnumMultiSelectHandler( + this DataTypePropertyBuilder> builder) + where TEnum : struct, Enum + { + return builder.SetHandler(new DefaultEnumMultiSelectHandler()); + } + + /// + /// Registers for a + /// property. Only the isAnyOf + /// operator is supported: rows are returned when the collection contains any + /// of the selected enum values. + /// + /// The entity type. + /// The enum type stored in the collection. + /// The property builder to configure. + /// The same builder instance for chaining. + public static DataTypePropertyBuilder> SetEnumMultiSelectHandler( + this DataTypePropertyBuilder> builder) + where TEnum : struct, Enum + { + return builder.SetHandler(new DefaultEnumMultiSelectHandler()); + } + public static DataTypePropertyBuilder SetNodeIdHandler( this DataTypePropertyBuilder builder, INodeIdSerializer idSerializer, diff --git a/tests/Hotchocolate.MuiDataGrid.Test/MuiDataGridEnumMultiSelectTests.cs b/tests/Hotchocolate.MuiDataGrid.Test/MuiDataGridEnumMultiSelectTests.cs index c59f38a..419072d 100644 --- a/tests/Hotchocolate.MuiDataGrid.Test/MuiDataGridEnumMultiSelectTests.cs +++ b/tests/Hotchocolate.MuiDataGrid.Test/MuiDataGridEnumMultiSelectTests.cs @@ -225,19 +225,21 @@ private sealed class TestAllInterfacesDataType : DataType builder) { - builder.Property(r => r.RequiredPermissions).SetEnumMultiSelectHandler(); // ICollection - builder.Property(r => r.GrantedPermissions).SetEnumMultiSelectHandler(); // IEnumerable - builder.Property(r => r.DeniedPermissions).SetEnumMultiSelectHandler(); // List + builder.Property(r => r.RequiredPermissions).SetEnumMultiSelectHandler(); // ICollection + builder.Property(r => r.GrantedPermissions).SetEnumMultiSelectHandler(); // IEnumerable + builder.Property(r => r.DeniedPermissions).SetEnumMultiSelectHandler(); // List } } private class UserWithRoles { +#pragma warning disable CA1822 // Mark members as static public int Id { get; set; } public string Name { get; set; } = string.Empty; public IList Roles { get; set; } = new List(); +#pragma warning restore CA1822 // Mark members as static } private class ResourceWithPermissions