@@ -52,14 +52,19 @@ describe('Combobox', () => {
5252 const enter = ( modifierKeys ?: { } ) => keydown ( 'Enter' , modifierKeys ) ;
5353 const escape = ( modifierKeys ?: { } ) => keydown ( 'Escape' , modifierKeys ) ;
5454
55- function setupCombobox ( opts : { filterMode ?: 'manual' | 'auto-select' | 'highlight' } = { } ) {
55+ function setupCombobox (
56+ opts : { readonly ?: boolean ; filterMode ?: 'manual' | 'auto-select' | 'highlight' } = { } ,
57+ ) {
5658 TestBed . configureTestingModule ( { } ) ;
5759 fixture = TestBed . createComponent ( ComboboxListboxExample ) ;
5860 const testComponent = fixture . componentInstance ;
5961
6062 if ( opts . filterMode ) {
6163 testComponent . filterMode . set ( opts . filterMode ) ;
6264 }
65+ if ( opts . readonly ) {
66+ testComponent . readonly . set ( true ) ;
67+ }
6368
6469 fixture . detectChanges ( ) ;
6570 defineTestVariables ( ) ;
@@ -526,6 +531,35 @@ describe('Combobox', () => {
526531 } ) ;
527532 } ) ;
528533
534+ describe ( 'Readonly' , ( ) => {
535+ beforeEach ( ( ) => setupCombobox ( { readonly : true } ) ) ;
536+
537+ it ( 'should close on selection' , ( ) => {
538+ focus ( ) ;
539+ down ( ) ;
540+ click ( getOption ( 'Alabama' ) ! ) ;
541+ expect ( inputElement . value ) . toBe ( 'Alabama' ) ;
542+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
543+ } ) ;
544+
545+ it ( 'should close on escape' , ( ) => {
546+ focus ( ) ;
547+ down ( ) ;
548+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
549+ escape ( ) ;
550+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
551+ } ) ;
552+
553+ it ( 'should clear selection on escape when closed' , ( ) => {
554+ focus ( ) ;
555+ down ( ) ;
556+ enter ( ) ;
557+ expect ( inputElement . value ) . toBe ( 'Alabama' ) ;
558+ escape ( ) ;
559+ expect ( inputElement . value ) . toBe ( '' ) ;
560+ } ) ;
561+ } ) ;
562+
529563 // describe('with programmatic value changes', () => {
530564 // // TODO(wagnermaciel): Figure out if there's a way to automatically update the
531565 // // input value when the popup value signal is updated programmatically.
@@ -590,14 +624,19 @@ describe('Combobox', () => {
590624 const enter = ( modifierKeys ?: { } ) => keydown ( 'Enter' , modifierKeys ) ;
591625 const escape = ( modifierKeys ?: { } ) => keydown ( 'Escape' , modifierKeys ) ;
592626
593- function setupCombobox ( opts : { filterMode ?: 'manual' | 'auto-select' | 'highlight' } = { } ) {
627+ function setupCombobox (
628+ opts : { readonly ?: boolean ; filterMode ?: 'manual' | 'auto-select' | 'highlight' } = { } ,
629+ ) {
594630 TestBed . configureTestingModule ( { } ) ;
595631 fixture = TestBed . createComponent ( ComboboxTreeExample ) ;
596632 const testComponent = fixture . componentInstance ;
597633
598634 if ( opts . filterMode ) {
599635 testComponent . filterMode . set ( opts . filterMode ) ;
600636 }
637+ if ( opts . readonly ) {
638+ testComponent . readonly . set ( true ) ;
639+ }
601640
602641 fixture . detectChanges ( ) ;
603642 defineTestVariables ( ) ;
@@ -1053,6 +1092,40 @@ describe('Combobox', () => {
10531092 expect ( getTreeItem ( 'August' ) ! . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
10541093 } ) ;
10551094 } ) ;
1095+
1096+ describe ( 'Readonly' , ( ) => {
1097+ beforeEach ( ( ) => setupCombobox ( { readonly : true } ) ) ;
1098+
1099+ it ( 'should close on selection' , ( ) => {
1100+ focus ( ) ;
1101+ down ( ) ;
1102+ right ( ) ;
1103+ right ( ) ;
1104+ enter ( ) ;
1105+ expect ( inputElement . value ) . toBe ( 'December' ) ;
1106+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
1107+ } ) ;
1108+
1109+ it ( 'should close on escape' , ( ) => {
1110+ focus ( ) ;
1111+ down ( ) ;
1112+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
1113+ escape ( ) ;
1114+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
1115+ } ) ;
1116+
1117+ it ( 'should clear selection on escape when closed' , ( ) => {
1118+ focus ( ) ;
1119+ down ( ) ;
1120+ right ( ) ;
1121+ right ( ) ;
1122+ enter ( ) ;
1123+ expect ( inputElement . value ) . toBe ( 'December' ) ;
1124+ expect ( inputElement . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
1125+ escape ( ) ;
1126+ expect ( inputElement . value ) . toBe ( '' ) ;
1127+ } ) ;
1128+ } ) ;
10561129 } ) ;
10571130} ) ;
10581131
@@ -1061,6 +1134,7 @@ describe('Combobox', () => {
10611134<div
10621135 ngCombobox
10631136 #combobox="ngCombobox"
1137+ [readonly]="readonly()"
10641138 [filterMode]="filterMode()"
10651139>
10661140 <input
@@ -1087,12 +1161,11 @@ describe('Combobox', () => {
10871161 imports : [ Combobox , ComboboxInput , ComboboxPopup , ComboboxPopupContainer , Listbox , Option ] ,
10881162} )
10891163class ComboboxListboxExample {
1164+ readonly = signal ( false ) ;
1165+ searchString = signal ( '' ) ;
10901166 value = signal < string [ ] > ( [ ] ) ;
1091-
10921167 filterMode = signal < 'manual' | 'auto-select' | 'highlight' > ( 'manual' ) ;
10931168
1094- searchString = signal ( '' ) ;
1095-
10961169 options = computed ( ( ) =>
10971170 states . filter ( state => state . toLowerCase ( ) . startsWith ( this . searchString ( ) . toLowerCase ( ) ) ) ,
10981171 ) ;
@@ -1103,6 +1176,7 @@ class ComboboxListboxExample {
11031176<div
11041177 ngCombobox
11051178 #combobox="ngCombobox"
1179+ [readonly]="readonly()"
11061180 [firstMatch]="firstMatch()"
11071181 [filterMode]="filterMode()"
11081182>
@@ -1157,13 +1231,11 @@ class ComboboxListboxExample {
11571231 ] ,
11581232} )
11591233class ComboboxTreeExample {
1160- value = signal < string [ ] > ( [ ] ) ;
1161-
1162- filterMode = signal < 'manual' | 'auto-select' | 'highlight' > ( 'manual' ) ;
1163-
1234+ readonly = signal ( false ) ;
11641235 searchString = signal ( '' ) ;
1165-
1236+ value = signal < string [ ] > ( [ ] ) ;
11661237 nodes = computed ( ( ) => this . filterTreeNodes ( TREE_NODES ) ) ;
1238+ filterMode = signal < 'manual' | 'auto-select' | 'highlight' > ( 'manual' ) ;
11671239
11681240 firstMatch = computed < string | undefined > ( ( ) => {
11691241 const flatNodes = this . flattenTreeNodes ( this . nodes ( ) ) ;
0 commit comments