@@ -525,111 +525,115 @@ describe('Combobox', () => {
525525 } ) ;
526526 } ) ;
527527
528- describe ( 'Readonly' , ( ) => {
529- beforeEach ( async ( ) => await setupCombobox ( ComboboxListboxExample , { readonly : true } ) ) ;
528+ describe ( 'Interactivity and Disablement (disabled, softDisabled, readonly)' , ( ) => {
529+ describe ( 'Readonly' , ( ) => {
530+ beforeEach ( async ( ) => await setupCombobox ( ComboboxListboxExample , { readonly : true } ) ) ;
530531
531- it ( 'should close on selection' , async ( ) => {
532- await focus ( ) ;
533- await down ( ) ;
534- await click ( getOption ( 'Alabama' ) ! ) ;
535- expect ( inputElement . value ) . toBe ( 'Alabama' ) ;
536- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
537- } ) ;
532+ it ( 'should set native readonly attribute on editable inputs without assigning disabled' , ( ) => {
533+ expect ( inputElement . hasAttribute ( 'readonly' ) ) . toBe ( true ) ;
534+ expect ( inputElement . hasAttribute ( 'disabled' ) ) . toBe ( false ) ;
535+ } ) ;
538536
539- it ( 'should close on escape' , async ( ) => {
540- await focus ( ) ;
541- await down ( ) ;
542- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
543- await escape ( ) ;
544- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
545- } ) ;
546- } ) ;
537+ it ( 'should suppress typing when readonly' , async ( ) => {
538+ await focus ( ) ;
539+ inputElement . value = 'New' ;
540+ inputElement . dispatchEvent ( new Event ( 'input' ) ) ;
541+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
542+ } ) ;
547543
548- describe ( 'Always Expanded' , ( ) => {
549- beforeEach ( async ( ) => await setupCombobox ( ) ) ;
544+ it ( 'should block expansion on arrow down when readonly' , async ( ) => {
545+ await focus ( ) ;
546+ await down ( ) ;
547+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
548+ } ) ;
549+ } ) ;
550550
551- it ( 'should not close on escape when alwaysExpanded is true' , async ( ) => {
552- fixture . componentInstance . alwaysExpanded . set ( true ) ;
553- await fixture . whenStable ( ) ;
554- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
551+ describe ( 'Disabled (Soft and Hard)' , ( ) => {
552+ beforeEach ( async ( ) => await setupCombobox ( ) ) ;
555553
556- await escape ( ) ;
557- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( ' true' ) ;
558- } ) ;
554+ it ( 'should keep the input focusable by default when disabled' , async ( ) => {
555+ fixture . componentInstance . disabled . set ( true ) ;
556+ await fixture . whenStable ( ) ;
559557
560- it ( 'should automatically report as expanded when alwaysExpanded is true' , async ( ) => {
561- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
562- fixture . componentInstance . alwaysExpanded . set ( true ) ;
563- await fixture . whenStable ( ) ;
564- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
565- } ) ;
566- } ) ;
558+ expect ( inputElement . disabled ) . toBe ( false ) ;
559+ expect ( inputElement . getAttribute ( 'disabled' ) ) . toBeNull ( ) ;
560+ expect ( inputElement . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) ;
561+ } ) ;
567562
568- describe ( 'Disabled' , ( ) => {
569- beforeEach ( async ( ) => await setupCombobox ( ) ) ;
563+ it ( 'should NOT set readonly when disabled and softDisabled is true' , async ( ) => {
564+ fixture . componentInstance . disabled . set ( true ) ;
565+ await fixture . whenStable ( ) ;
570566
571- it ( 'should keep the input focusable by default when disabled' , async ( ) => {
572- fixture . componentInstance . disabled . set ( true ) ;
573- await fixture . whenStable ( ) ;
567+ expect ( inputElement . hasAttribute ( 'readonly' ) ) . toBe ( false ) ;
568+ } ) ;
574569
575- expect ( inputElement . disabled ) . toBe ( false ) ;
576- expect ( inputElement . getAttribute ( 'disabled' ) ) . toBeNull ( ) ;
577- expect ( inputElement . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) ;
578- } ) ;
570+ it ( 'should block interactions when disabled' , async ( ) => {
571+ fixture . componentInstance . disabled . set ( true ) ;
572+ await fixture . whenStable ( ) ;
579573
580- it ( 'should make the input read-only when disabled and softDisabled is true' , async ( ) => {
581- fixture . componentInstance . disabled . set ( true ) ;
582- await fixture . whenStable ( ) ;
574+ await focus ( ) ;
575+ await keydown ( 'ArrowDown' ) ;
576+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
577+ } ) ;
583578
584- expect ( inputElement . getAttribute ( 'readonly' ) ) . toBe ( '' ) ;
585- } ) ;
579+ it ( 'should make the input unfocusable when softDisabled is false' , async ( ) => {
580+ fixture . componentInstance . disabled . set ( true ) ;
581+ fixture . componentInstance . softDisabled . set ( false ) ;
582+ await fixture . whenStable ( ) ;
586583
587- it ( 'should block interactions when disabled' , async ( ) => {
588- fixture . componentInstance . disabled . set ( true ) ;
589- await fixture . whenStable ( ) ;
584+ expect ( inputElement . disabled ) . toBe ( true ) ;
585+ expect ( inputElement . getAttribute ( 'disabled' ) ) . toBe ( '' ) ;
586+ expect ( inputElement . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) ;
587+ } ) ;
590588
591- await focus ( ) ;
592- await keydown ( 'ArrowDown' ) ;
593- expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
594- } ) ;
589+ it ( 'should respect user-defined tabindex when softDisabled is true' , async ( ) => {
590+ fixture . componentInstance . disabled . set ( true ) ;
591+ fixture . componentInstance . tabIndex . set ( 0 ) ;
592+ await fixture . whenStable ( ) ;
595593
596- it ( 'should make the input unfocusable when softDisabled is false' , async ( ) => {
597- fixture . componentInstance . disabled . set ( true ) ;
598- fixture . componentInstance . softDisabled . set ( false ) ;
599- await fixture . whenStable ( ) ;
594+ expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
595+ } ) ;
600596
601- expect ( inputElement . disabled ) . toBe ( true ) ;
602- expect ( inputElement . getAttribute ( 'disabled' ) ) . toBe ( '' ) ;
603- expect ( inputElement . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) ;
604- } ) ;
597+ it ( 'should respect user-defined tabindex when not disabled' , async ( ) => {
598+ fixture . componentInstance . tabIndex . set ( 0 ) ;
599+ await fixture . whenStable ( ) ;
605600
606- it ( 'should respect user-defined tabindex when softDisabled is true' , async ( ) => {
607- fixture . componentInstance . disabled . set ( true ) ;
608- fixture . componentInstance . tabIndex . set ( 0 ) ;
609- await fixture . whenStable ( ) ;
601+ expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
602+ } ) ;
610603
611- expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
612- } ) ;
604+ it ( 'should default to tabindex 0 when not disabled' , async ( ) => {
605+ await fixture . whenStable ( ) ;
606+ expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
607+ } ) ;
613608
614- it ( 'should respect user-defined tabindex when not disabled' , async ( ) => {
615- fixture . componentInstance . tabIndex . set ( 0 ) ;
616- await fixture . whenStable ( ) ;
609+ it ( 'should force tabindex to -1 when hard-disabled, ignoring user-defined tabindex' , async ( ) => {
610+ fixture . componentInstance . disabled . set ( true ) ;
611+ fixture . componentInstance . softDisabled . set ( false ) ;
612+ fixture . componentInstance . tabIndex . set ( 0 ) ;
613+ await fixture . whenStable ( ) ;
617614
618- expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
615+ expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '-1' ) ;
616+ } ) ;
619617 } ) ;
618+ } ) ;
620619
621- it ( 'should default to tabindex 0 when not disabled' , async ( ) => {
620+ describe ( 'Always Expanded' , ( ) => {
621+ beforeEach ( async ( ) => await setupCombobox ( ) ) ;
622+
623+ it ( 'should not close on escape when alwaysExpanded is true' , async ( ) => {
624+ fixture . componentInstance . alwaysExpanded . set ( true ) ;
622625 await fixture . whenStable ( ) ;
623- expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '0' ) ;
626+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
627+
628+ await escape ( ) ;
629+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
624630 } ) ;
625631
626- it ( 'should force tabindex to -1 when hard-disabled, ignoring user-defined tabindex' , async ( ) => {
627- fixture . componentInstance . disabled . set ( true ) ;
628- fixture . componentInstance . softDisabled . set ( false ) ;
629- fixture . componentInstance . tabIndex . set ( 0 ) ;
632+ it ( 'should automatically report as expanded when alwaysExpanded is true' , async ( ) => {
633+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
634+ fixture . componentInstance . alwaysExpanded . set ( true ) ;
630635 await fixture . whenStable ( ) ;
631-
632- expect ( inputElement . getAttribute ( 'tabindex' ) ) . toBe ( '-1' ) ;
636+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
633637 } ) ;
634638 } ) ;
635639 } ) ;
@@ -1341,7 +1345,8 @@ function getTreeNodes(): TreeNode[] {
13411345 placeholder="Search..."
13421346 [(value)]="searchString"
13431347 [(expanded)]="popupExpanded"
1344- [disabled]="readonly()"
1348+ [readonly]="readonly()"
1349+ [disabled]="disabled()"
13451350 (focusout)="onBlur()"
13461351 />
13471352
@@ -1394,6 +1399,7 @@ class ComboboxTreeExample {
13941399 readonly tree = viewChild ( Tree ) ;
13951400
13961401 readonly = signal ( false ) ;
1402+ disabled = signal ( false ) ;
13971403 popupExpanded = signal ( false ) ;
13981404 searchString = signal ( '' ) ;
13991405 value = signal < string [ ] > ( [ ] ) ;
@@ -1591,7 +1597,8 @@ class ComboboxGridExample {
15911597 placeholder="Search..."
15921598 [(value)]="searchString"
15931599 (input)="onInput()"
1594- [disabled]="readonly()"
1600+ [readonly]="readonly()"
1601+ [disabled]="disabled()"
15951602 (focusout)="onBlur()"
15961603 (click)="combobox.expanded.set(true)"
15971604 />
@@ -1611,6 +1618,7 @@ class ComboboxGridExample {
16111618} )
16121619class ComboboxListboxAutoSelectExample {
16131620 readonly = signal ( false ) ;
1621+ disabled = signal ( false ) ;
16141622 popupExpanded = signal ( false ) ;
16151623 searchString = signal ( '' ) ;
16161624 value = signal < string [ ] > ( [ ] ) ;
@@ -1656,7 +1664,8 @@ class ComboboxListboxAutoSelectExample {
16561664 [(value)]="searchString"
16571665 [(expanded)]="popupExpanded"
16581666 [inlineSuggestion]="value()[0] || options()[0]"
1659- [disabled]="readonly()"
1667+ [readonly]="readonly()"
1668+ [disabled]="disabled()"
16601669 (click)="popupExpanded.set(true)"
16611670 />
16621671
@@ -1676,6 +1685,7 @@ class ComboboxListboxAutoSelectExample {
16761685class ComboboxListboxHighlightExample {
16771686 readonly combobox = viewChild ( Combobox ) ;
16781687 readonly = signal ( false ) ;
1688+ disabled = signal ( false ) ;
16791689 popupExpanded = signal ( false ) ;
16801690 searchString = signal ( '' ) ;
16811691 value = signal < string [ ] > ( [ ] ) ;
0 commit comments