@@ -539,6 +539,219 @@ public async Task can_filter_nullable_enum_not_equals_null()
539539 people [ 0 ] . BirthMonth . Should ( ) . Be ( BirthMonthEnum . June ) ;
540540 }
541541
542+ [ Fact ]
543+ public async Task can_filter_nullable_string_with_case_insensitive_contains ( )
544+ {
545+ // Arrange
546+ var testingServiceScope = new TestingServiceScope ( ) ;
547+ var uniqueLastName = $ "CaseInsensitiveContainsTest_{ Guid . NewGuid ( ) } ";
548+ var personWithNullTitle = new FakeTestingPersonBuilder ( )
549+ . WithTitle ( null )
550+ . WithLastName ( uniqueLastName )
551+ . WithFirstName ( "NullTitle" )
552+ . Build ( ) ;
553+ var personWithMatchingTitle = new FakeTestingPersonBuilder ( )
554+ . WithTitle ( "Doctor Smith" )
555+ . WithLastName ( uniqueLastName )
556+ . WithFirstName ( "MatchingTitle" )
557+ . Build ( ) ;
558+ var personWithNonMatchingTitle = new FakeTestingPersonBuilder ( )
559+ . WithTitle ( "Mr. Jones" )
560+ . WithLastName ( uniqueLastName )
561+ . WithFirstName ( "NonMatchingTitle" )
562+ . Build ( ) ;
563+ await testingServiceScope . InsertAsync ( personWithNullTitle , personWithMatchingTitle , personWithNonMatchingTitle ) ;
564+
565+ var input = $ """ Title @=* "doctor" && LastName == "{ uniqueLastName } " """ ;
566+
567+ // Act
568+ var queryablePeople = testingServiceScope . DbContext ( ) . People ;
569+ var appliedQueryable = queryablePeople . ApplyQueryKitFilter ( input ) ;
570+ var people = await appliedQueryable . ToListAsync ( ) ;
571+
572+ // Assert
573+ people . Count . Should ( ) . Be ( 1 ) ;
574+ people [ 0 ] . Id . Should ( ) . Be ( personWithMatchingTitle . Id ) ;
575+ }
576+
577+ [ Fact ]
578+ public async Task can_filter_nullable_string_with_case_insensitive_starts_with ( )
579+ {
580+ // Arrange
581+ var testingServiceScope = new TestingServiceScope ( ) ;
582+ var uniqueLastName = $ "CaseInsensitiveStartsWithTest_{ Guid . NewGuid ( ) } ";
583+ var personWithNullTitle = new FakeTestingPersonBuilder ( )
584+ . WithTitle ( null )
585+ . WithLastName ( uniqueLastName )
586+ . WithFirstName ( "NullTitle" )
587+ . Build ( ) ;
588+ var personWithMatchingTitle = new FakeTestingPersonBuilder ( )
589+ . WithTitle ( "Doctor Smith" )
590+ . WithLastName ( uniqueLastName )
591+ . WithFirstName ( "MatchingTitle" )
592+ . Build ( ) ;
593+ var personWithNonMatchingTitle = new FakeTestingPersonBuilder ( )
594+ . WithTitle ( "Mr. Jones" )
595+ . WithLastName ( uniqueLastName )
596+ . WithFirstName ( "NonMatchingTitle" )
597+ . Build ( ) ;
598+ await testingServiceScope . InsertAsync ( personWithNullTitle , personWithMatchingTitle , personWithNonMatchingTitle ) ;
599+
600+ var input = $ """ Title _=* "doctor" && LastName == "{ uniqueLastName } " """ ;
601+
602+ // Act
603+ var queryablePeople = testingServiceScope . DbContext ( ) . People ;
604+ var appliedQueryable = queryablePeople . ApplyQueryKitFilter ( input ) ;
605+ var people = await appliedQueryable . ToListAsync ( ) ;
606+
607+ // Assert
608+ people . Count . Should ( ) . Be ( 1 ) ;
609+ people [ 0 ] . Id . Should ( ) . Be ( personWithMatchingTitle . Id ) ;
610+ }
611+
612+ [ Fact ]
613+ public async Task can_filter_nullable_string_with_case_insensitive_ends_with ( )
614+ {
615+ // Arrange
616+ var testingServiceScope = new TestingServiceScope ( ) ;
617+ var uniqueLastName = $ "CaseInsensitiveEndsWithTest_{ Guid . NewGuid ( ) } ";
618+ var personWithNullTitle = new FakeTestingPersonBuilder ( )
619+ . WithTitle ( null )
620+ . WithLastName ( uniqueLastName )
621+ . WithFirstName ( "NullTitle" )
622+ . Build ( ) ;
623+ var personWithMatchingTitle = new FakeTestingPersonBuilder ( )
624+ . WithTitle ( "Doctor Smith" )
625+ . WithLastName ( uniqueLastName )
626+ . WithFirstName ( "MatchingTitle" )
627+ . Build ( ) ;
628+ var personWithNonMatchingTitle = new FakeTestingPersonBuilder ( )
629+ . WithTitle ( "Mr. Jones" )
630+ . WithLastName ( uniqueLastName )
631+ . WithFirstName ( "NonMatchingTitle" )
632+ . Build ( ) ;
633+ await testingServiceScope . InsertAsync ( personWithNullTitle , personWithMatchingTitle , personWithNonMatchingTitle ) ;
634+
635+ var input = $ """ Title _-=* "smith" && LastName == "{ uniqueLastName } " """ ;
636+
637+ // Act
638+ var queryablePeople = testingServiceScope . DbContext ( ) . People ;
639+ var appliedQueryable = queryablePeople . ApplyQueryKitFilter ( input ) ;
640+ var people = await appliedQueryable . ToListAsync ( ) ;
641+
642+ // Assert
643+ people . Count . Should ( ) . Be ( 1 ) ;
644+ people [ 0 ] . Id . Should ( ) . Be ( personWithMatchingTitle . Id ) ;
645+ }
646+
647+ [ Fact ]
648+ public async Task can_filter_nullable_string_with_case_insensitive_equals ( )
649+ {
650+ // Arrange
651+ var testingServiceScope = new TestingServiceScope ( ) ;
652+ var uniqueLastName = $ "CaseInsensitiveEqualsTest_{ Guid . NewGuid ( ) } ";
653+ var personWithNullTitle = new FakeTestingPersonBuilder ( )
654+ . WithTitle ( null )
655+ . WithLastName ( uniqueLastName )
656+ . WithFirstName ( "NullTitle" )
657+ . Build ( ) ;
658+ var personWithMatchingTitle = new FakeTestingPersonBuilder ( )
659+ . WithTitle ( "Doctor" )
660+ . WithLastName ( uniqueLastName )
661+ . WithFirstName ( "MatchingTitle" )
662+ . Build ( ) ;
663+ var personWithNonMatchingTitle = new FakeTestingPersonBuilder ( )
664+ . WithTitle ( "Mr." )
665+ . WithLastName ( uniqueLastName )
666+ . WithFirstName ( "NonMatchingTitle" )
667+ . Build ( ) ;
668+ await testingServiceScope . InsertAsync ( personWithNullTitle , personWithMatchingTitle , personWithNonMatchingTitle ) ;
669+
670+ var input = $ """ Title ==* "doctor" && LastName == "{ uniqueLastName } " """ ;
671+
672+ // Act
673+ var queryablePeople = testingServiceScope . DbContext ( ) . People ;
674+ var appliedQueryable = queryablePeople . ApplyQueryKitFilter ( input ) ;
675+ var people = await appliedQueryable . ToListAsync ( ) ;
676+
677+ // Assert
678+ people . Count . Should ( ) . Be ( 1 ) ;
679+ people [ 0 ] . Id . Should ( ) . Be ( personWithMatchingTitle . Id ) ;
680+ }
681+
682+ [ Fact ]
683+ public async Task can_filter_nullable_string_with_case_insensitive_not_equals ( )
684+ {
685+ // Arrange
686+ var testingServiceScope = new TestingServiceScope ( ) ;
687+ var uniqueLastName = $ "CaseInsensitiveNotEqualsTest_{ Guid . NewGuid ( ) } ";
688+ var personWithNullTitle = new FakeTestingPersonBuilder ( )
689+ . WithTitle ( null )
690+ . WithLastName ( uniqueLastName )
691+ . WithFirstName ( "NullTitle" )
692+ . Build ( ) ;
693+ var personWithMatchingTitle = new FakeTestingPersonBuilder ( )
694+ . WithTitle ( "Doctor" )
695+ . WithLastName ( uniqueLastName )
696+ . WithFirstName ( "MatchingTitle" )
697+ . Build ( ) ;
698+ var personWithNonMatchingTitle = new FakeTestingPersonBuilder ( )
699+ . WithTitle ( "Mr." )
700+ . WithLastName ( uniqueLastName )
701+ . WithFirstName ( "NonMatchingTitle" )
702+ . Build ( ) ;
703+ await testingServiceScope . InsertAsync ( personWithNullTitle , personWithMatchingTitle , personWithNonMatchingTitle ) ;
704+
705+ // Title !=* "doctor" should return records where Title is not "doctor" (case-insensitive)
706+ // null values should be treated as non-matching (i.e. they don't equal "doctor")
707+ var input = $ """ Title !=* "doctor" && LastName == "{ uniqueLastName } " """ ;
708+
709+ // Act
710+ var queryablePeople = testingServiceScope . DbContext ( ) . People ;
711+ var appliedQueryable = queryablePeople . ApplyQueryKitFilter ( input ) ;
712+ var people = await appliedQueryable . ToListAsync ( ) ;
713+
714+ // Assert - should return null title and "Mr." title (both are != "doctor")
715+ people . Count . Should ( ) . Be ( 2 ) ;
716+ people . Should ( ) . Contain ( p => p . Id == personWithNullTitle . Id ) ;
717+ people . Should ( ) . Contain ( p => p . Id == personWithNonMatchingTitle . Id ) ;
718+ }
719+
720+ [ Fact ]
721+ public async Task can_filter_nullable_string_with_case_insensitive_in_operator ( )
722+ {
723+ // Arrange
724+ var testingServiceScope = new TestingServiceScope ( ) ;
725+ var uniqueLastName = $ "CaseInsensitiveInTest_{ Guid . NewGuid ( ) } ";
726+ var personWithNullTitle = new FakeTestingPersonBuilder ( )
727+ . WithTitle ( null )
728+ . WithLastName ( uniqueLastName )
729+ . WithFirstName ( "NullTitle" )
730+ . Build ( ) ;
731+ var personWithMatchingTitle = new FakeTestingPersonBuilder ( )
732+ . WithTitle ( "Doctor" )
733+ . WithLastName ( uniqueLastName )
734+ . WithFirstName ( "MatchingTitle" )
735+ . Build ( ) ;
736+ var personWithNonMatchingTitle = new FakeTestingPersonBuilder ( )
737+ . WithTitle ( "Mr." )
738+ . WithLastName ( uniqueLastName )
739+ . WithFirstName ( "NonMatchingTitle" )
740+ . Build ( ) ;
741+ await testingServiceScope . InsertAsync ( personWithNullTitle , personWithMatchingTitle , personWithNonMatchingTitle ) ;
742+
743+ var input = $ """ Title ^^* ["doctor", "professor"] && LastName == "{ uniqueLastName } " """ ;
744+
745+ // Act
746+ var queryablePeople = testingServiceScope . DbContext ( ) . People ;
747+ var appliedQueryable = queryablePeople . ApplyQueryKitFilter ( input ) ;
748+ var people = await appliedQueryable . ToListAsync ( ) ;
749+
750+ // Assert
751+ people . Count . Should ( ) . Be ( 1 ) ;
752+ people [ 0 ] . Id . Should ( ) . Be ( personWithMatchingTitle . Id ) ;
753+ }
754+
542755 [ Fact ]
543756 public async Task can_filter_with_null_in_complex_expression ( )
544757 {
0 commit comments