@@ -80,7 +80,7 @@ ruleTester.run("prefer-disabledfocusable-over-disabled", rule as unknown as Rule
8080 "<Checkbox disabled={true} disabledFocusable={false} pending />" ,
8181 "<ToggleButton disabled={isDisabled} disabledFocusable={shouldFocus} busy />" ,
8282
83- // ✅ Complex expressions
83+ // ✅ Complex expressions with disabledFocusable
8484 "<Button disabledFocusable={shouldDisable} loading={isSubmitting}>Submit</Button>" ,
8585 "<Input disabledFocusable={disabled && !error} isLoading={fetching} />" ,
8686 "<SpinButton disabledFocusable={!enabled || hasError} pending={processing} />" ,
@@ -113,7 +113,31 @@ ruleTester.run("prefer-disabledfocusable-over-disabled", rule as unknown as Rule
113113 "<DatePicker>Normal DatePicker</DatePicker>" ,
114114 "<TimePicker>Normal TimePicker</TimePicker>" ,
115115 "<Link>Normal Link</Link>" ,
116- "<Tab>Normal Tab</Tab>"
116+ "<Tab>Normal Tab</Tab>" ,
117+
118+ // ✅ Test components with different casing variations
119+ "<button disabled loading>Submit</button>" , // lowercase (should not trigger - not a FluentUI component)
120+ "<BUTTON disabled loading>Submit</BUTTON>" , // uppercase (should not trigger - not a FluentUI component)
121+
122+ // ✅ Cases where loading props are truly empty (null, undefined, empty string)
123+ '<Input disabled={true} isLoading="">Submit</Input>' , // loading is empty string
124+ "<Checkbox disabled={true} pending={null} />" , // loading is null
125+ "<SpinButton disabled={true} busy={undefined} />" , // loading is undefined
126+
127+ // ✅ Cases where only one prop exists (no combination to trigger rule)
128+ "<Button disabled={(() => false)()}>Submit</Button>" , // only disabled, no loading
129+ "<Input isLoading={status === 'loading'} />" , // only loading, no disabled
130+ "<Checkbox loading={(() => true)()} />" , // only loading, no disabled
131+ "<ToggleButton disabled={isDisabled ? false : true} />" , // only disabled, no loading
132+
133+ // ✅ JSXSpreadAttribute cases without both props
134+ "<Button {...props} disabled={false}>Submit</Button>" , // only disabled, no loading
135+ "<Input {...buttonProps} isLoading={1} />" , // only loading, no disabled
136+
137+ // ✅ Cases where disabled has truly empty values
138+ '<Button disabled="" loading={false}>Submit</Button>' , // disabled is empty string (should not trigger because disabled is empty)
139+ "<Input disabled={null} loading={true} />" , // disabled is null (should not trigger because disabled is empty)
140+ "<Checkbox disabled={undefined} pending={true} />" // disabled is undefined (should not trigger because disabled is empty)
117141 ] ,
118142
119143 invalid : [
@@ -134,26 +158,55 @@ ruleTester.run("prefer-disabledfocusable-over-disabled", rule as unknown as Rule
134158 output : "<Checkbox disabledFocusable pending />"
135159 } ,
136160
137- // ❌ Test case where getLoadingStateProp returns null (should use preferDisabledFocusableGeneric)
138- // This shouldn't happen in practice but tests the fallback
161+ // ❌ Boolean prop values (ALL boolean values are considered "non-empty" by hasNonEmptyProp)
139162 {
140- code : "<Button disabled loading>Submit</Button>" ,
163+ code : "<Button disabled={true} loading={true} >Submit</Button>" ,
141164 errors : [ { messageId : "preferDisabledFocusable" } ] ,
142- output : "<Button disabledFocusable loading>Submit</Button>"
165+ output : "<Button disabledFocusable={true} loading={true} >Submit</Button>"
143166 } ,
144-
145- // ❌ Boolean prop values
146167 {
147- code : "<Button disabled={true} loading={true }>Submit</Button>" ,
168+ code : "<Button disabled={true} loading={false }>Submit</Button>" , // MOVED FROM VALID - false is still non-empty!
148169 errors : [ { messageId : "preferDisabledFocusable" } ] ,
149- output : "<Button disabledFocusable={true} loading={true }>Submit</Button>"
170+ output : "<Button disabledFocusable={true} loading={false }>Submit</Button>"
150171 } ,
151172 {
152173 code : "<Input disabled={false} isLoading={processing} />" ,
153174 errors : [ { messageId : "preferDisabledFocusable" } ] ,
154175 output : "<Input disabledFocusable={false} isLoading={processing} />"
155176 } ,
156177
178+ // ❌ Cases that were incorrectly in valid section (hasNonEmptyProp treats these as non-empty)
179+ {
180+ code : "<Button disabled={(() => false)()} loading={(() => true)()}>Submit</Button>" ,
181+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
182+ output : "<Button disabledFocusable={(() => false)()} loading={(() => true)()}>Submit</Button>"
183+ } ,
184+ {
185+ code : "<Input disabled={isDisabled ? false : true} isLoading={status === 'loading'} />" ,
186+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
187+ output : "<Input disabledFocusable={isDisabled ? false : true} isLoading={status === 'loading'} />"
188+ } ,
189+ {
190+ code : "<Button {...props} disabled={false} loading>Submit</Button>" ,
191+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
192+ output : "<Button {...props} disabledFocusable={false} loading>Submit</Button>"
193+ } ,
194+ {
195+ code : "<Input {...buttonProps} disabled loading={false} />" ,
196+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
197+ output : "<Input {...buttonProps} disabledFocusable loading={false} />"
198+ } ,
199+ {
200+ code : "<Button disabled={0} loading>Submit</Button>" , // 0 is considered non-empty
201+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
202+ output : "<Button disabledFocusable={0} loading>Submit</Button>"
203+ } ,
204+ {
205+ code : "<Input disabled={false} isLoading={1} />" ,
206+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
207+ output : "<Input disabledFocusable={false} isLoading={1} />"
208+ } ,
209+
157210 // ❌ Expression prop values
158211 {
159212 code : "<SpinButton disabled={isDisabled} loading={isSubmitting} />" ,
@@ -317,6 +370,72 @@ ruleTester.run("prefer-disabledfocusable-over-disabled", rule as unknown as Rule
317370 code : "<Checkbox disabled={shouldDisable || isReadonly} busy={processing} />" ,
318371 errors : [ { messageId : "preferDisabledFocusable" } ] ,
319372 output : "<Checkbox disabledFocusable={shouldDisable || isReadonly} busy={processing} />"
373+ } ,
374+
375+ // ❌ Test more complex expression scenarios
376+ {
377+ code : "<Button disabled={isSubmitting || hasError} loading={status === 'pending'}>Submit</Button>" ,
378+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
379+ output : "<Button disabledFocusable={isSubmitting || hasError} loading={status === 'pending'}>Submit</Button>"
380+ } ,
381+
382+ // ❌ Additional test cases to ensure full coverage
383+ {
384+ code : "<Button disabled={true} loading>Submit</Button>" ,
385+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
386+ output : "<Button disabledFocusable={true} loading>Submit</Button>"
387+ } ,
388+
389+ // ❌ Test edge cases for fix function coverage
390+ {
391+ code : "<Button disabled={1} loading={true}>Submit</Button>" , // truthy number
392+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
393+ output : "<Button disabledFocusable={1} loading={true}>Submit</Button>"
394+ } ,
395+ {
396+ code : "<Input disabled={'true'} isLoading={'false'} />" , // string values
397+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
398+ output : "<Input disabledFocusable={'true'} isLoading={'false'} />"
399+ } ,
400+
401+ // ❌ Test JSXSpreadAttribute with invalid cases
402+ {
403+ code : "<Button {...props} disabled loading>Submit</Button>" ,
404+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
405+ output : "<Button {...props} disabledFocusable loading>Submit</Button>"
406+ } ,
407+ {
408+ code : "<Input disabled {...inputProps} isLoading />" ,
409+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
410+ output : "<Input disabledFocusable {...inputProps} isLoading />"
411+ } ,
412+
413+ // ❌ Test cases where fix function edge cases might be triggered
414+ {
415+ code : "<Button disabled={variable} pending={anotherVariable}>Submit</Button>" ,
416+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
417+ output : "<Button disabledFocusable={variable} pending={anotherVariable}>Submit</Button>"
418+ } ,
419+
420+ // ❌ Test more combinations to increase coverage
421+ {
422+ code : "<Combobox disabled={true} loading={true} pending={false} />" , // Multiple loading props
423+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
424+ output : "<Combobox disabledFocusable={true} loading={true} pending={false} />"
425+ } ,
426+
427+ // ❌ Edge case: Test with numeric and other literal values
428+ {
429+ code : "<Button disabled={42} loading={-1}>Submit</Button>" , // Numeric values
430+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
431+ output : "<Button disabledFocusable={42} loading={-1}>Submit</Button>"
432+ } ,
433+
434+ // ❌ Test string prop values that are non-empty
435+ {
436+ code : '<Button disabled="true" loading="false">Submit</Button>' ,
437+ errors : [ { messageId : "preferDisabledFocusable" } ] ,
438+ output : '<Button disabledFocusable="true" loading="false">Submit</Button>'
320439 }
321440 ]
322441} ) ;
0 commit comments