@@ -540,4 +540,142 @@ describe("predicateUrlPatterns", () => {
540540 expect ( predicateUrlPatterns ( differentUrl , patterns ) ) . toBe ( true ) ;
541541 } ) ;
542542 } ) ;
543+
544+ describe ( "with search patterns" , ( ) => {
545+ test ( "returns true when URL matches an allow pattern with search params" , ( ) => {
546+ const patterns : KnockGuideActivationUrlPattern [ ] = [
547+ {
548+ directive : "allow" ,
549+ pattern : new URLPattern ( { pathname : "/dashboard" , search : "tab=settings" } ) ,
550+ } ,
551+ ] ;
552+
553+ const matchingUrl = new URL ( "https://example.com/dashboard?tab=settings" ) ;
554+ const nonMatchingUrl = new URL ( "https://example.com/dashboard?tab=overview" ) ;
555+
556+ expect ( predicateUrlPatterns ( matchingUrl , patterns ) ) . toBe ( true ) ;
557+ expect ( predicateUrlPatterns ( nonMatchingUrl , patterns ) ) . toBe ( undefined ) ;
558+ } ) ;
559+
560+ test ( "returns false when URL matches a block pattern with search params" , ( ) => {
561+ const patterns : KnockGuideActivationUrlPattern [ ] = [
562+ {
563+ directive : "block" ,
564+ pattern : new URLPattern ( { pathname : "/admin" , search : "mode=debug" } ) ,
565+ } ,
566+ ] ;
567+
568+ const matchingUrl = new URL ( "https://example.com/admin?mode=debug" ) ;
569+ const nonMatchingUrl = new URL ( "https://example.com/admin?mode=normal" ) ;
570+
571+ expect ( predicateUrlPatterns ( matchingUrl , patterns ) ) . toBe ( false ) ;
572+ expect ( predicateUrlPatterns ( nonMatchingUrl , patterns ) ) . toBe ( true ) ;
573+ } ) ;
574+
575+ test ( "handles wildcard patterns in search params" , ( ) => {
576+ const patterns : KnockGuideActivationUrlPattern [ ] = [
577+ {
578+ directive : "allow" ,
579+ pattern : new URLPattern ( { pathname : "/page" , search : "id=*" } ) ,
580+ } ,
581+ ] ;
582+
583+ const matchingUrl = new URL ( "https://example.com/page?id=123" ) ;
584+ const anotherMatchingUrl = new URL ( "https://example.com/page?id=abc" ) ;
585+ const nonMatchingUrl = new URL ( "https://example.com/page?other=value" ) ;
586+
587+ expect ( predicateUrlPatterns ( matchingUrl , patterns ) ) . toBe ( true ) ;
588+ expect ( predicateUrlPatterns ( anotherMatchingUrl , patterns ) ) . toBe ( true ) ;
589+ expect ( predicateUrlPatterns ( nonMatchingUrl , patterns ) ) . toBe ( undefined ) ;
590+ } ) ;
591+
592+ test ( "matches when pathname matches but no search pattern specified" , ( ) => {
593+ const patterns : KnockGuideActivationUrlPattern [ ] = [
594+ {
595+ directive : "allow" ,
596+ pattern : new URLPattern ( { pathname : "/dashboard" } ) ,
597+ } ,
598+ ] ;
599+
600+ // Should match regardless of search params when no search pattern specified
601+ const urlWithSearch = new URL ( "https://example.com/dashboard?tab=settings" ) ;
602+ const urlWithoutSearch = new URL ( "https://example.com/dashboard" ) ;
603+
604+ expect ( predicateUrlPatterns ( urlWithSearch , patterns ) ) . toBe ( true ) ;
605+ expect ( predicateUrlPatterns ( urlWithoutSearch , patterns ) ) . toBe ( true ) ;
606+ } ) ;
607+
608+ test ( "block pattern with search takes precedence over allow pattern without search" , ( ) => {
609+ const patterns : KnockGuideActivationUrlPattern [ ] = [
610+ {
611+ directive : "allow" ,
612+ pattern : new URLPattern ( { pathname : "/settings/*" } ) ,
613+ } ,
614+ {
615+ directive : "block" ,
616+ pattern : new URLPattern ( { pathname : "/settings/admin" , search : "dangerous=true" } ) ,
617+ } ,
618+ ] ;
619+
620+ const blockedUrl = new URL ( "https://example.com/settings/admin?dangerous=true" ) ;
621+ const allowedUrl = new URL ( "https://example.com/settings/admin?dangerous=false" ) ;
622+
623+ expect ( predicateUrlPatterns ( blockedUrl , patterns ) ) . toBe ( false ) ;
624+ expect ( predicateUrlPatterns ( allowedUrl , patterns ) ) . toBe ( true ) ;
625+ } ) ;
626+
627+ test ( "handles multiple search params in pattern" , ( ) => {
628+ const patterns : KnockGuideActivationUrlPattern [ ] = [
629+ {
630+ directive : "allow" ,
631+ pattern : new URLPattern ( { pathname : "/report" , search : "type=sales&year=2024" } ) ,
632+ } ,
633+ ] ;
634+
635+ const matchingUrl = new URL ( "https://example.com/report?type=sales&year=2024" ) ;
636+ const partialMatchUrl = new URL ( "https://example.com/report?type=sales" ) ;
637+ const wrongOrderUrl = new URL ( "https://example.com/report?year=2024&type=sales" ) ;
638+
639+ expect ( predicateUrlPatterns ( matchingUrl , patterns ) ) . toBe ( true ) ;
640+ expect ( predicateUrlPatterns ( partialMatchUrl , patterns ) ) . toBe ( undefined ) ;
641+ // URLPattern is sensitive to search param order
642+ expect ( predicateUrlPatterns ( wrongOrderUrl , patterns ) ) . toBe ( undefined ) ;
643+ } ) ;
644+
645+ test ( "handles multiple search params in pattern, to match a single search param regardless of the order" , ( ) => {
646+ const patterns : KnockGuideActivationUrlPattern [ ] = [
647+ {
648+ directive : "allow" ,
649+ pattern : new URLPattern ( { pathname : "/report" , search : "*role=admin*" } ) ,
650+ } ,
651+ ] ;
652+
653+ const url1 = new URL ( "https://example.com/report?role=admin" ) ;
654+ const url2 = new URL ( "https://example.com/report?year=2022&role=admin" ) ;
655+ const url3 = new URL ( "https://example.com/report?role=admin&year=2022" ) ;
656+ const url4 = new URL ( "https://example.com/report?location=nyc&role=admin&year=2022" ) ;
657+ const url5 = new URL ( "https://example.com/report?location=nyc&year=2022" ) ;
658+
659+ expect ( predicateUrlPatterns ( url1 , patterns ) ) . toBe ( true ) ;
660+ expect ( predicateUrlPatterns ( url2 , patterns ) ) . toBe ( true ) ;
661+ expect ( predicateUrlPatterns ( url3 , patterns ) ) . toBe ( true ) ;
662+ expect ( predicateUrlPatterns ( url4 , patterns ) ) . toBe ( true ) ;
663+ expect ( predicateUrlPatterns ( url5 , patterns ) ) . toBe ( undefined ) ;
664+ } ) ;
665+
666+ test ( "handles search pattern with wildcard for any search params" , ( ) => {
667+ const patterns : KnockGuideActivationUrlPattern [ ] = [
668+ {
669+ directive : "block" ,
670+ pattern : new URLPattern ( { pathname : "/api" , search : "*" } ) ,
671+ } ,
672+ ] ;
673+
674+ const urlWithSearch = new URL ( "https://example.com/api?key=value" ) ;
675+ const urlWithoutSearch = new URL ( "https://example.com/api" ) ;
676+
677+ expect ( predicateUrlPatterns ( urlWithSearch , patterns ) ) . toBe ( false ) ;
678+ expect ( predicateUrlPatterns ( urlWithoutSearch , patterns ) ) . toBe ( false ) ;
679+ } ) ;
680+ } ) ;
543681} ) ;
0 commit comments