@@ -488,3 +488,119 @@ describe("FindingsTreeProvider — findings cache invalidation", () => {
488488 expect ( roots [ 0 ] . kind === "group" && roots [ 0 ] . label ) . toBe ( "CRITICAL" ) ;
489489 } ) ;
490490} ) ;
491+
492+ describe ( "FindingsTreeProvider — filter" , ( ) => {
493+ // The filter narrows the visible tree to findings whose rule ID,
494+ // message, or fsPath contains the filter string (case-insensitive).
495+ // The badge tracks the filtered count; `lastFindingUris` keeps the
496+ // full set so the batch-touches-us check still wakes us up for
497+ // publishes that would currently be filtered out (otherwise a
498+ // CLEAR of a filtered-out URI would never refresh).
499+
500+ function fakeTreeView ( ) : { badge : unknown } & object {
501+ return { badge : undefined } ;
502+ }
503+
504+ it ( "defaults to no filter (getFilter returns empty string)" , ( ) => {
505+ const p = new FindingsTreeProvider ( ctx ) ;
506+ expect ( p . getFilter ( ) ) . toBe ( "" ) ;
507+ } ) ;
508+
509+ it ( "setFilter narrows the tree to matching rule IDs" , ( ) => {
510+ setStubDiagnostics ( [
511+ { file : "a.yml" , rule : "GHA-001" , severity : "HIGH" } ,
512+ { file : "b.yml" , rule : "GHA-015" , severity : "HIGH" } ,
513+ { file : "c.yml" , rule : "GLI-002" , severity : "HIGH" } ,
514+ ] ) ;
515+ const p = new FindingsTreeProvider ( ctx ) ;
516+ p . setGroupMode ( "severity" ) ;
517+ expect ( p . getChildren ( ) [ 0 ] ) . toMatchObject ( { kind : "group" } ) ;
518+ expect ( ( p . getChildren ( ) [ 0 ] as unknown as { children : unknown [ ] } ) . children ) . toHaveLength (
519+ 3 ,
520+ ) ;
521+
522+ p . setFilter ( "GHA" ) ;
523+ const after = p . getChildren ( ) [ 0 ] as unknown as { children : unknown [ ] } ;
524+ expect ( after . children ) . toHaveLength ( 2 ) ;
525+ } ) ;
526+
527+ it ( "filter is case-insensitive" , ( ) => {
528+ setStubDiagnostics ( [
529+ { file : "a.yml" , rule : "GHA-001" , severity : "HIGH" } ,
530+ ] ) ;
531+ const p = new FindingsTreeProvider ( ctx ) ;
532+ p . setGroupMode ( "severity" ) ;
533+ p . setFilter ( "gha" ) ;
534+ const roots = p . getChildren ( ) ;
535+ expect ( roots ) . toHaveLength ( 1 ) ;
536+ } ) ;
537+
538+ it ( "filter matches the message body, not just the rule ID" , ( ) => {
539+ setStubDiagnostics ( [
540+ { file : "a.yml" , rule : "GHA-001" , severity : "HIGH" } ,
541+ { file : "b.yml" , rule : "GHA-002" , severity : "HIGH" } ,
542+ ] ) ;
543+ // Both findings have message "GHA-001 title" / "GHA-002 title"
544+ // because setStubDiagnostics builds the message from the rule.
545+ // Filtering on "title" should keep both.
546+ const p = new FindingsTreeProvider ( ctx ) ;
547+ p . setGroupMode ( "severity" ) ;
548+ p . setFilter ( "title" ) ;
549+ const roots = p . getChildren ( ) ;
550+ expect ( ( roots [ 0 ] as unknown as { children : unknown [ ] } ) . children ) . toHaveLength ( 2 ) ;
551+ } ) ;
552+
553+ it ( "filter matches the fsPath" , ( ) => {
554+ setStubDiagnostics ( [
555+ { file : "workflows/ci.yml" , rule : "GHA-001" , severity : "HIGH" } ,
556+ { file : "config/dockerfile" , rule : "DOCK-001" , severity : "HIGH" } ,
557+ ] ) ;
558+ const p = new FindingsTreeProvider ( ctx ) ;
559+ p . setGroupMode ( "severity" ) ;
560+ p . setFilter ( "workflows" ) ;
561+ expect ( ( p . getChildren ( ) [ 0 ] as unknown as { children : unknown [ ] } ) . children ) . toHaveLength (
562+ 1 ,
563+ ) ;
564+ } ) ;
565+
566+ it ( "empty filter clears the narrowing" , ( ) => {
567+ setStubDiagnostics ( [
568+ { file : "a.yml" , rule : "GHA-001" , severity : "HIGH" } ,
569+ { file : "b.yml" , rule : "GLI-002" , severity : "HIGH" } ,
570+ ] ) ;
571+ const p = new FindingsTreeProvider ( ctx ) ;
572+ p . setGroupMode ( "severity" ) ;
573+ p . setFilter ( "GHA" ) ;
574+ expect ( ( p . getChildren ( ) [ 0 ] as unknown as { children : unknown [ ] } ) . children ) . toHaveLength (
575+ 1 ,
576+ ) ;
577+ p . setFilter ( "" ) ;
578+ expect ( ( p . getChildren ( ) [ 0 ] as unknown as { children : unknown [ ] } ) . children ) . toHaveLength (
579+ 2 ,
580+ ) ;
581+ } ) ;
582+
583+ it ( "setFilter trims whitespace before comparing for change" , ( ) => {
584+ setStubDiagnostics ( [
585+ { file : "a.yml" , rule : "GHA-001" , severity : "HIGH" } ,
586+ ] ) ;
587+ const p = new FindingsTreeProvider ( ctx ) ;
588+ p . setFilter ( " GHA " ) ;
589+ expect ( p . getFilter ( ) ) . toBe ( "GHA" ) ;
590+ } ) ;
591+
592+ it ( "badge reflects the filtered count, not the workspace total" , ( ) => {
593+ setStubDiagnostics ( [
594+ { file : "a.yml" , rule : "GHA-001" , severity : "HIGH" } ,
595+ { file : "b.yml" , rule : "GLI-002" , severity : "HIGH" } ,
596+ { file : "c.yml" , rule : "GHA-003" , severity : "HIGH" } ,
597+ ] ) ;
598+ const p = new FindingsTreeProvider ( ctx ) ;
599+ const view = fakeTreeView ( ) ;
600+ p . setTreeView ( view as unknown as Parameters < typeof p . setTreeView > [ 0 ] ) ;
601+ expect ( ( view . badge as { value : number } ) . value ) . toBe ( 3 ) ;
602+
603+ p . setFilter ( "GHA" ) ;
604+ expect ( ( view . badge as { value : number } ) . value ) . toBe ( 2 ) ;
605+ } ) ;
606+ } ) ;
0 commit comments