@@ -17,7 +17,13 @@ import {
1717 hasAssociatedLabelViaHtmlFor ,
1818 isInsideLabelTag
1919} from "../../../../lib/util/labelUtils" ;
20- import { hasAccessibleLabel , LabeledControlConfig , makeLabeledControlRule } from "../../../../lib/util/ruleFactory" ;
20+ import {
21+ hasAccessibleLabel ,
22+ LabeledControlConfig ,
23+ makeLabeledControlRule ,
24+ generateAutoFix ,
25+ AutoFixConfig
26+ } from "../../../../lib/util/ruleFactory" ;
2127
2228jest . mock ( "../../../../lib/util/hasDefinedProp" , ( ) => ( {
2329 hasDefinedProp : jest . fn ( )
@@ -526,3 +532,219 @@ describe("makeLabeledControlRule (RuleTester integration)", () => {
526532 } ) ;
527533 } ) ;
528534} ) ;
535+
536+ // Tests for auto-fix functionality
537+ describe ( "generateAutoFix" , ( ) => {
538+ const mockFixer = {
539+ insertTextAfter : jest . fn ( ) . mockReturnValue ( { type : "fix" } ) ,
540+ insertTextBefore : jest . fn ( ) . mockReturnValue ( { type : "fix" } ) ,
541+ replaceText : jest . fn ( ) . mockReturnValue ( { type : "fix" } ) ,
542+ replaceTextRange : jest . fn ( ) . mockReturnValue ( { type : "fix" } ) ,
543+ removeRange : jest . fn ( ) . mockReturnValue ( { type : "fix" } )
544+ } as unknown as TSESLint . RuleFixer ;
545+
546+ const mockOpening = {
547+ type : AST_NODE_TYPES . JSXOpeningElement ,
548+ name : {
549+ type : AST_NODE_TYPES . JSXIdentifier ,
550+ name : "Button" ,
551+ range : [ 0 , 6 ] as [ number , number ] ,
552+ loc : { } as any
553+ } ,
554+ attributes : [ ] ,
555+ selfClosing : false ,
556+ range : [ 0 , 8 ] as [ number , number ] ,
557+ loc : { } as any
558+ } as TSESTree . JSXOpeningElement ;
559+
560+ beforeEach ( ( ) => {
561+ jest . clearAllMocks ( ) ;
562+ } ) ;
563+
564+ test ( "returns null when config is falsy" , ( ) => {
565+ const result = generateAutoFix ( mockOpening , null as any ) ;
566+ expect ( result ) . toBeNull ( ) ;
567+ } ) ;
568+
569+ test ( "aria-label-placeholder strategy" , ( ) => {
570+ const config : AutoFixConfig = {
571+ strategy : "aria-label-placeholder"
572+ } ;
573+
574+ const fixFunction = generateAutoFix ( mockOpening , config ) ;
575+ expect ( fixFunction ) . toBeDefined ( ) ;
576+
577+ const result = fixFunction ! ( mockFixer ) ;
578+ expect ( mockFixer . insertTextAfter ) . toHaveBeenCalledWith ( mockOpening . name , ' aria-label=""' ) ;
579+ expect ( result ) . toEqual ( { type : "fix" } ) ;
580+ } ) ;
581+
582+ test ( "aria-label-suggestion strategy with default label" , ( ) => {
583+ const config : AutoFixConfig = {
584+ strategy : "aria-label-suggestion"
585+ } ;
586+
587+ const fixFunction = generateAutoFix ( mockOpening , config ) ;
588+ expect ( fixFunction ) . toBeDefined ( ) ;
589+
590+ const result = fixFunction ! ( mockFixer ) ;
591+ expect ( mockFixer . insertTextAfter ) . toHaveBeenCalledWith ( mockOpening . name , ' aria-label="Provide accessible name"' ) ;
592+ expect ( result ) . toEqual ( { type : "fix" } ) ;
593+ } ) ;
594+
595+ test ( "aria-label-suggestion strategy with custom label" , ( ) => {
596+ const config : AutoFixConfig = {
597+ strategy : "aria-label-suggestion" ,
598+ suggestedLabel : "Custom button label"
599+ } ;
600+
601+ const fixFunction = generateAutoFix ( mockOpening , config ) ;
602+ expect ( fixFunction ) . toBeDefined ( ) ;
603+
604+ const result = fixFunction ! ( mockFixer ) ;
605+ expect ( mockFixer . insertTextAfter ) . toHaveBeenCalledWith ( mockOpening . name , ' aria-label="Custom button label"' ) ;
606+ expect ( result ) . toEqual ( { type : "fix" } ) ;
607+ } ) ;
608+
609+ test ( "add-required-prop strategy with propName only" , ( ) => {
610+ const config : AutoFixConfig = {
611+ strategy : "add-required-prop" ,
612+ propName : "alt"
613+ } ;
614+
615+ const fixFunction = generateAutoFix ( mockOpening , config ) ;
616+ expect ( fixFunction ) . toBeDefined ( ) ;
617+
618+ const result = fixFunction ! ( mockFixer ) ;
619+ expect ( mockFixer . insertTextAfter ) . toHaveBeenCalledWith ( mockOpening . name , ' alt=""' ) ;
620+ expect ( result ) . toEqual ( { type : "fix" } ) ;
621+ } ) ;
622+
623+ test ( "add-required-prop strategy with propName and propValue" , ( ) => {
624+ const config : AutoFixConfig = {
625+ strategy : "add-required-prop" ,
626+ propName : "role" ,
627+ propValue : '"button"'
628+ } ;
629+
630+ const fixFunction = generateAutoFix ( mockOpening , config ) ;
631+ expect ( fixFunction ) . toBeDefined ( ) ;
632+
633+ const result = fixFunction ! ( mockFixer ) ;
634+ expect ( mockFixer . insertTextAfter ) . toHaveBeenCalledWith ( mockOpening . name , ' role="button"' ) ;
635+ expect ( result ) . toEqual ( { type : "fix" } ) ;
636+ } ) ;
637+
638+ test ( "add-required-prop strategy returns null when propName is missing" , ( ) => {
639+ const config : AutoFixConfig = {
640+ strategy : "add-required-prop"
641+ // propName is missing
642+ } ;
643+
644+ const fixFunction = generateAutoFix ( mockOpening , config ) ;
645+ expect ( fixFunction ) . toBeDefined ( ) ;
646+
647+ const result = fixFunction ! ( mockFixer ) ;
648+ expect ( result ) . toBeNull ( ) ;
649+ expect ( mockFixer . insertTextAfter ) . not . toHaveBeenCalled ( ) ;
650+ } ) ;
651+
652+ test ( "custom strategy with customFix function" , ( ) => {
653+ const mockCustomFix = jest . fn ( ) . mockReturnValue ( ' custom-attribute="value"' ) ;
654+ const config : AutoFixConfig = {
655+ strategy : "custom" ,
656+ customFix : mockCustomFix
657+ } ;
658+
659+ const fixFunction = generateAutoFix ( mockOpening , config ) ;
660+ expect ( fixFunction ) . toBeDefined ( ) ;
661+
662+ const result = fixFunction ! ( mockFixer ) ;
663+ expect ( mockCustomFix ) . toHaveBeenCalledWith ( mockOpening ) ;
664+ expect ( mockFixer . insertTextAfter ) . toHaveBeenCalledWith ( mockOpening . name , ' custom-attribute="value"' ) ;
665+ expect ( result ) . toEqual ( { type : "fix" } ) ;
666+ } ) ;
667+
668+ test ( "custom strategy returns null when customFix is missing" , ( ) => {
669+ const config : AutoFixConfig = {
670+ strategy : "custom"
671+ // customFix is missing
672+ } ;
673+
674+ const fixFunction = generateAutoFix ( mockOpening , config ) ;
675+ expect ( fixFunction ) . toBeDefined ( ) ;
676+
677+ const result = fixFunction ! ( mockFixer ) ;
678+ expect ( result ) . toBeNull ( ) ;
679+ expect ( mockFixer . insertTextAfter ) . not . toHaveBeenCalled ( ) ;
680+ } ) ;
681+
682+ test ( "default case returns null for unknown strategy" , ( ) => {
683+ const config : AutoFixConfig = {
684+ strategy : "unknown-strategy" as any
685+ } ;
686+
687+ const fixFunction = generateAutoFix ( mockOpening , config ) ;
688+ expect ( fixFunction ) . toBeDefined ( ) ;
689+
690+ const result = fixFunction ! ( mockFixer ) ;
691+ expect ( result ) . toBeNull ( ) ;
692+ expect ( mockFixer . insertTextAfter ) . not . toHaveBeenCalled ( ) ;
693+ } ) ;
694+ } ) ;
695+
696+ // Tests for auto-fix integration in makeLabeledControlRule
697+ describe ( "makeLabeledControlRule with auto-fix" , ( ) => {
698+ beforeEach ( ( ) => {
699+ resetAllMocksToFalse ( ) ;
700+ } ) ;
701+
702+ test ( "generateAutoFix integration - fix function is generated correctly" , ( ) => {
703+ // Test the integration by directly calling generateAutoFix with mock data
704+ const mockOpening = {
705+ type : AST_NODE_TYPES . JSXOpeningElement ,
706+ name : {
707+ type : AST_NODE_TYPES . JSXIdentifier ,
708+ name : "Button" ,
709+ range : [ 0 , 6 ] as [ number , number ] ,
710+ loc : { } as any
711+ } ,
712+ attributes : [ ] ,
713+ selfClosing : false ,
714+ range : [ 0 , 8 ] as [ number , number ] ,
715+ loc : { } as any
716+ } as TSESTree . JSXOpeningElement ;
717+
718+ const config : AutoFixConfig = {
719+ strategy : "aria-label-placeholder"
720+ } ;
721+
722+ const fixFunction = generateAutoFix ( mockOpening , config ) ;
723+ expect ( fixFunction ) . toBeDefined ( ) ;
724+ expect ( typeof fixFunction ) . toBe ( "function" ) ;
725+
726+ // Test that the fix function works when called
727+ const mockFixer = {
728+ insertTextAfter : jest . fn ( ) . mockReturnValue ( { type : "fix" } )
729+ } as unknown as TSESLint . RuleFixer ;
730+
731+ const result = fixFunction ! ( mockFixer ) ;
732+ expect ( mockFixer . insertTextAfter ) . toHaveBeenCalledWith ( mockOpening . name , ' aria-label=""' ) ;
733+ expect ( result ) . toEqual ( { type : "fix" } ) ;
734+ } ) ;
735+
736+ test ( "generateAutoFix with null config returns null" , ( ) => {
737+ const mockOpening = {
738+ type : AST_NODE_TYPES . JSXOpeningElement ,
739+ name : {
740+ type : AST_NODE_TYPES . JSXIdentifier ,
741+ name : "Button" ,
742+ range : [ 0 , 6 ] as [ number , number ] ,
743+ loc : { } as any
744+ }
745+ } as TSESTree . JSXOpeningElement ;
746+
747+ const result = generateAutoFix ( mockOpening , null as any ) ;
748+ expect ( result ) . toBeNull ( ) ;
749+ } ) ;
750+ } ) ;
0 commit comments