diff --git a/src/AutoFilterer/Attributes/ToLowerEqualsComparisonAttribute.cs b/src/AutoFilterer/Attributes/ToLowerEqualsComparisonAttribute.cs new file mode 100644 index 0000000..47e9005 --- /dev/null +++ b/src/AutoFilterer/Attributes/ToLowerEqualsComparisonAttribute.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace AutoFilterer.Attributes; + +/// +/// This class generates equal query, for string fields. +/// +public class ToLowerEqualsComparisonAttribute : FilteringOptionsBaseAttribute +{ + public override Expression BuildExpression(ExpressionBuildContext context) + { + var equalsMethod = typeof(string).GetMethod(nameof(string.Equals), types: new[] { typeof(string) }); + + var toLowerMethod = typeof(string).GetMethod(nameof(string.ToLower), types: new Type[0]); + + var comparison = Expression.Equal( + Expression.Call( + method: equalsMethod, + instance: Expression.Call(method: toLowerMethod, instance: Expression.Property(context.ExpressionBody, context.TargetProperty.Name) + ), + arguments: new[] { Expression.Call(method: toLowerMethod, instance: context.FilterPropertyExpression) }), + Expression.Constant(true)); + + return comparison; + } +} diff --git a/tests/AutoFilterer.Tests/Attributes/ToLowerEqualsComparisonAttributeTests.cs b/tests/AutoFilterer.Tests/Attributes/ToLowerEqualsComparisonAttributeTests.cs new file mode 100644 index 0000000..f35d186 --- /dev/null +++ b/tests/AutoFilterer.Tests/Attributes/ToLowerEqualsComparisonAttributeTests.cs @@ -0,0 +1,208 @@ +using AutoFilterer.Extensions; +using AutoFilterer.Tests.Core; +using AutoFilterer.Tests.Environment.Dtos; +using AutoFilterer.Tests.Environment.Models; +using System.Collections.Generic; +using System.Linq; +using Xunit; +using AutoFilterer.Attributes; +using AutoFilterer.Types; +using System; + +namespace AutoFilterer.Tests.Attributes; + +public class ToLowerEqualsComparisonAttributeTests +{ + [Theory, AutoMoqData(count: 16)] + public void BuildExpression_ShouldGenerateQueryCorrect_WithAttribute(List dummyData) + { + // Arrange + var filter = new BookFilter_LowerEquals + { + Title = "Test Book" + }; + IQueryable query = dummyData.AsQueryable(); + + // Act + var filteredQuery = query.ApplyFilter(filter); + var result = filteredQuery.ToList(); + + // Assert + var actualResult = query.Where(x => x.Title.ToLower().Equals(filter.Title.ToLower())).ToList(); + + Assert.Equal(result.Count, actualResult.Count); + } + + [Theory, AutoMoqData(count: 16)] + public void BuildExpression_ShouldBeCaseInsensitive(List dummyData) + { + // Arrange + var filter = new BookFilter_LowerEquals + { + Title = "TEST BOOK" + }; + IQueryable query = dummyData.AsQueryable(); + + // Act + var filteredQuery = query.ApplyFilter(filter); + var result = filteredQuery.ToList(); + + // Assert + var actualResult = query.Where(x => x.Title.ToLower().Equals(filter.Title.ToLower())).ToList(); + + Assert.Equal(result.Count, actualResult.Count); + } + + [Theory, AutoMoqData(count: 16)] + public void BuildExpression_ShouldHandleEmptyString(List dummyData) + { + // Arrange + var filter = new BookFilter_LowerEquals + { + Title = "" + }; + IQueryable query = dummyData.AsQueryable(); + + // Act + var filteredQuery = query.ApplyFilter(filter); + var result = filteredQuery.ToList(); + + // Assert + var actualResult = query.Where(x => x.Title.ToLower().Equals(filter.Title.ToLower())).ToList(); + + Assert.Equal(result.Count, actualResult.Count); + } + + [Theory, AutoMoqData(count: 16)] + public void BuildExpression_ShouldHandleNullValue(List dummyData) + { + // Arrange + var filter = new BookFilter_LowerEquals + { + Title = null + }; + IQueryable query = dummyData.AsQueryable(); + + // Act + var filteredQuery = query.ApplyFilter(filter); + var result = filteredQuery.ToList(); + + // Assert + // When filter value is null, the framework should skip the filter entirely + // and return all results (no filtering applied) + Assert.Equal(dummyData.Count, result.Count); + } + + [Theory, AutoMoqData(count: 16)] + public void BuildExpression_ShouldHandleSpecialCharacters(List dummyData) + { + // Arrange + var filter = new BookFilter_LowerEquals + { + Title = "Test-Book_123" + }; + IQueryable query = dummyData.AsQueryable(); + + // Act + var filteredQuery = query.ApplyFilter(filter); + var result = filteredQuery.ToList(); + + // Assert + var actualResult = query.Where(x => x.Title.ToLower().Equals(filter.Title.ToLower())).ToList(); + + Assert.Equal(result.Count, actualResult.Count); + } + + [Theory, AutoMoqData(count: 16)] + public void BuildExpression_ShouldHandleWhitespace(List dummyData) + { + // Arrange + var filter = new BookFilter_LowerEquals + { + Title = " Test Book " + }; + IQueryable query = dummyData.AsQueryable(); + + // Act + var filteredQuery = query.ApplyFilter(filter); + var result = filteredQuery.ToList(); + + // Assert + var actualResult = query.Where(x => x.Title.ToLower().Equals(filter.Title.ToLower())).ToList(); + + Assert.Equal(result.Count, actualResult.Count); + } + + [Theory, AutoMoqData(count: 16)] + public void BuildExpression_ShouldHandleUnicodeCharacters(List dummyData) + { + // Arrange + var filter = new BookFilter_LowerEquals + { + Title = "Café Book" + }; + IQueryable query = dummyData.AsQueryable(); + + // Act + var filteredQuery = query.ApplyFilter(filter); + var result = filteredQuery.ToList(); + + // Assert + var actualResult = query.Where(x => x.Title.ToLower().Equals(filter.Title.ToLower())).ToList(); + + Assert.Equal(result.Count, actualResult.Count); + } + + [Theory, AutoMoqData(count: 16)] + public void BuildExpression_ShouldReturnEmptyResult_WhenNoMatch(List dummyData) + { + // Arrange + var filter = new BookFilter_LowerEquals + { + Title = "NonExistentBook" + }; + IQueryable query = dummyData.AsQueryable(); + + // Act + var filteredQuery = query.ApplyFilter(filter); + var result = filteredQuery.ToList(); + + // Assert + var actualResult = query.Where(x => x.Title.ToLower().Equals(filter.Title.ToLower())).ToList(); + + Assert.Equal(result.Count, actualResult.Count); + Assert.Empty(result); + } + + [Theory, AutoMoqData(count: 16)] + public void BuildExpression_ShouldReturnExactMatchesOnly(List dummyData) + { + // Arrange + var filter = new BookFilter_LowerEquals + { + Title = "Test" + }; + IQueryable query = dummyData.AsQueryable(); + + // Act + var filteredQuery = query.ApplyFilter(filter); + var result = filteredQuery.ToList(); + + // Assert + var actualResult = query.Where(x => x.Title.ToLower().Equals(filter.Title.ToLower())).ToList(); + + Assert.Equal(result.Count, actualResult.Count); + + // Verify that all results are exact matches (not partial matches) + foreach (var book in result) + { + Assert.Equal(filter.Title.ToLower(), book.Title.ToLower()); + } + } +} + +public class BookFilter_LowerEquals : FilterBase +{ + [ToLowerEqualsComparison] + public string Title { get; set; } +} \ No newline at end of file