@@ -145,32 +145,56 @@ describe('MatSelect', () => {
145145 select = fixture . debugElement . query ( By . css ( 'mat-select' ) ) ! . nativeElement ;
146146 } ) ) ;
147147
148- it ( 'should set the role of the select to listbox' , fakeAsync ( ( ) => {
149- expect ( select . getAttribute ( 'role' ) ) . toEqual ( 'listbox' ) ;
148+ it ( 'should set the role of the select to combobox' , fakeAsync ( ( ) => {
149+ expect ( select . getAttribute ( 'role' ) ) . toEqual ( 'combobox' ) ;
150+ expect ( select . getAttribute ( 'aria-autocomplete' ) ) . toBe ( 'none' ) ;
151+ expect ( select . getAttribute ( 'aria-haspopup' ) ) . toBe ( 'listbox' ) ;
150152 } ) ) ;
151153
152- it ( 'should set the aria label of the select to the placeholder' , fakeAsync ( ( ) => {
153- expect ( select . getAttribute ( 'aria-label' ) ) . toEqual ( 'Food' ) ;
154+ it ( 'should point the aria-controls attribute to the listbox' , fakeAsync ( ( ) => {
155+ fixture . componentInstance . select . open ( ) ;
156+ fixture . detectChanges ( ) ;
157+ flush ( ) ;
158+
159+ const ariaControls = select . getAttribute ( 'aria-controls' ) ;
160+ expect ( ariaControls ) . toBeTruthy ( ) ;
161+ expect ( ariaControls ) . toBe ( document . querySelector ( '.mat-select-panel' ) ! . id ) ;
162+ } ) ) ;
163+
164+ it ( 'should set aria-expanded based on the select open state' , fakeAsync ( ( ) => {
165+ expect ( select . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
166+
167+ fixture . componentInstance . select . open ( ) ;
168+ fixture . detectChanges ( ) ;
169+ flush ( ) ;
170+
171+ expect ( select . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
154172 } ) ) ;
155173
156174 it ( 'should support setting a custom aria-label' , fakeAsync ( ( ) => {
157175 fixture . componentInstance . ariaLabel = 'Custom Label' ;
158176 fixture . detectChanges ( ) ;
159177
160178 expect ( select . getAttribute ( 'aria-label' ) ) . toEqual ( 'Custom Label' ) ;
179+ expect ( select . hasAttribute ( 'aria-labelledby' ) ) . toBeFalsy ( ) ;
161180 } ) ) ;
162181
163- it ( 'should not set an aria-label if aria-labelledby is specified ' , fakeAsync ( ( ) => {
182+ it ( 'should be able to add an extra aria-labelledby on top of the default ' , fakeAsync ( ( ) => {
164183 fixture . componentInstance . ariaLabelledby = 'myLabelId' ;
165184 fixture . detectChanges ( ) ;
166185
167- expect ( select . getAttribute ( 'aria-label' ) ) . toBeFalsy ( 'Expected no aria-label to be set.' ) ;
168- expect ( select . getAttribute ( 'aria-labelledby' ) ) . toBe ( 'myLabelId' ) ;
186+ const labelId = fixture . nativeElement . querySelector ( '.mat-form-field-label' ) . id ;
187+ const valueId = fixture . nativeElement . querySelector ( '.mat-select-value' ) . id ;
188+
189+ expect ( select . getAttribute ( 'aria-labelledby' ) ) . toBe ( `${ labelId } ${ valueId } myLabelId` ) ;
169190 } ) ) ;
170191
171- it ( 'should not have aria-labelledby in the DOM if it`s not specified ' , fakeAsync ( ( ) => {
192+ it ( 'should set aria-labelledby to the value and label IDs ' , fakeAsync ( ( ) => {
172193 fixture . detectChanges ( ) ;
173- expect ( select . hasAttribute ( 'aria-labelledby' ) ) . toBeFalsy ( ) ;
194+
195+ const labelId = fixture . nativeElement . querySelector ( '.mat-form-field-label' ) . id ;
196+ const valueId = fixture . nativeElement . querySelector ( '.mat-select-value' ) . id ;
197+ expect ( select . getAttribute ( 'aria-labelledby' ) ) . toBe ( `${ labelId } ${ valueId } ` ) ;
174198 } ) ) ;
175199
176200 it ( 'should set the tabindex of the select to 0 by default' , fakeAsync ( ( ) => {
@@ -237,37 +261,15 @@ describe('MatSelect', () => {
237261 expect ( select . getAttribute ( 'tabindex' ) ) . toEqual ( '0' ) ;
238262 } ) ) ;
239263
240- it ( 'should set `aria-labelledby` to form field label if there is no placeholder' , ( ) => {
241- fixture . destroy ( ) ;
242-
243- const labelFixture = TestBed . createComponent ( SelectWithFormFieldLabel ) ;
244- labelFixture . detectChanges ( ) ;
245- select = labelFixture . debugElement . query ( By . css ( 'mat-select' ) ) ! . nativeElement ;
246-
247- expect ( select . getAttribute ( 'aria-labelledby' ) ) . toBeTruthy ( ) ;
248- expect ( select . getAttribute ( 'aria-labelledby' ) )
249- . toBe ( labelFixture . nativeElement . querySelector ( 'label' ) . getAttribute ( 'id' ) ) ;
250- } ) ;
251-
252- it ( 'should not set `aria-labelledby` if there is a placeholder' , ( ) => {
253- fixture . destroy ( ) ;
254-
255- const labelFixture = TestBed . createComponent ( SelectWithFormFieldLabel ) ;
256- labelFixture . componentInstance . placeholder = 'Thing selector' ;
257- labelFixture . detectChanges ( ) ;
258- select = labelFixture . debugElement . query ( By . css ( 'mat-select' ) ) ! . nativeElement ;
259-
260- expect ( select . getAttribute ( 'aria-labelledby' ) ) . toBeFalsy ( ) ;
261- } ) ;
262-
263- it ( 'should not set `aria-labelledby` if there is no form field label' , ( ) => {
264+ it ( 'should set `aria-labelledby` to the value ID if there is no form field' , ( ) => {
264265 fixture . destroy ( ) ;
265266
266267 const labelFixture = TestBed . createComponent ( SelectWithChangeEvent ) ;
267268 labelFixture . detectChanges ( ) ;
268269 select = labelFixture . debugElement . query ( By . css ( 'mat-select' ) ) ! . nativeElement ;
270+ const valueId = labelFixture . nativeElement . querySelector ( '.mat-select-value' ) . id ;
269271
270- expect ( select . getAttribute ( 'aria-labelledby' ) ) . toBeFalsy ( ) ;
272+ expect ( select . getAttribute ( 'aria-labelledby' ) ?. trim ( ) ) . toBe ( valueId ) ;
271273 } ) ;
272274
273275 it ( 'should select options via the UP/DOWN arrow keys on a closed select' , fakeAsync ( ( ) => {
@@ -812,28 +814,28 @@ describe('MatSelect', () => {
812814 expect ( document . activeElement ) . toBe ( select , 'Expected select element to be focused.' ) ;
813815 } ) ) ;
814816
815- // Having `aria-hidden` on the trigger avoids issues where
816- // screen readers read out the wrong amount of options.
817- it ( 'should set aria-hidden on the trigger element' , fakeAsync ( ( ) => {
818- const trigger = fixture . debugElement . query ( By . css ( '.mat-select-trigger' ) ) ! . nativeElement ;
819-
820- expect ( trigger . getAttribute ( 'aria-hidden' ) )
821- . toBe ( 'true' , 'Expected aria-hidden to be true when the select is open.' ) ;
822- } ) ) ;
823-
824- it ( 'should set `aria-multiselectable` to true on multi-select instances' , fakeAsync ( ( ) => {
825- fixture . destroy ( ) ;
826-
827- const multiFixture = TestBed . createComponent ( MultiSelect ) ;
817+ it ( 'should set `aria-multiselectable` to true on the listbox inside multi select' ,
818+ fakeAsync ( ( ) => {
819+ fixture . destroy ( ) ;
828820
829- multiFixture . detectChanges ( ) ;
830- select = multiFixture . debugElement . query ( By . css ( 'mat-select' ) ) ! . nativeElement ;
821+ const multiFixture = TestBed . createComponent ( MultiSelect ) ;
822+ multiFixture . detectChanges ( ) ;
823+ select = multiFixture . debugElement . query ( By . css ( 'mat-select' ) ) ! . nativeElement ;
824+ multiFixture . componentInstance . select . open ( ) ;
825+ multiFixture . detectChanges ( ) ;
826+ flush ( ) ;
831827
832- expect ( select . getAttribute ( 'aria-multiselectable' ) ) . toBe ( 'true' ) ;
833- } ) ) ;
828+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
829+ expect ( panel . getAttribute ( 'aria-multiselectable' ) ) . toBe ( 'true' ) ;
830+ } ) ) ;
834831
835832 it ( 'should set aria-multiselectable false on single-selection instances' , fakeAsync ( ( ) => {
836- expect ( select . getAttribute ( 'aria-multiselectable' ) ) . toBe ( 'false' ) ;
833+ fixture . componentInstance . select . open ( ) ;
834+ fixture . detectChanges ( ) ;
835+ flush ( ) ;
836+
837+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
838+ expect ( panel . getAttribute ( 'aria-multiselectable' ) ) . toBe ( 'false' ) ;
837839 } ) ) ;
838840
839841 it ( 'should set aria-activedescendant only while the panel is open' , fakeAsync ( ( ) => {
@@ -929,6 +931,47 @@ describe('MatSelect', () => {
929931 expect ( document . activeElement ) . toBe ( select , 'Expected trigger to be focused.' ) ;
930932 } ) ) ;
931933
934+ it ( 'should set a role of listbox on the select panel' , fakeAsync ( ( ) => {
935+ fixture . componentInstance . select . open ( ) ;
936+ fixture . detectChanges ( ) ;
937+ flush ( ) ;
938+
939+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
940+ expect ( panel . getAttribute ( 'role' ) ) . toBe ( 'listbox' ) ;
941+ } ) ) ;
942+
943+ it ( 'should point the aria-labelledby of the panel to the field label' , fakeAsync ( ( ) => {
944+ fixture . componentInstance . select . open ( ) ;
945+ fixture . detectChanges ( ) ;
946+ flush ( ) ;
947+
948+ const labelId = fixture . nativeElement . querySelector ( '.mat-form-field-label' ) . id ;
949+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
950+ expect ( panel . getAttribute ( 'aria-labelledby' ) ) . toBe ( labelId ) ;
951+ } ) ) ;
952+
953+ it ( 'should add a custom aria-labelledby to the panel' , fakeAsync ( ( ) => {
954+ fixture . componentInstance . ariaLabelledby = 'myLabelId' ;
955+ fixture . componentInstance . select . open ( ) ;
956+ fixture . detectChanges ( ) ;
957+ flush ( ) ;
958+
959+ const labelId = fixture . nativeElement . querySelector ( '.mat-form-field-label' ) . id ;
960+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
961+ expect ( panel . getAttribute ( 'aria-labelledby' ) ) . toBe ( `${ labelId } myLabelId` ) ;
962+ } ) ) ;
963+
964+ it ( 'should clear aria-labelledby from the panel if an aria-label is set' , fakeAsync ( ( ) => {
965+ fixture . componentInstance . ariaLabel = 'My label' ;
966+ fixture . componentInstance . select . open ( ) ;
967+ fixture . detectChanges ( ) ;
968+ flush ( ) ;
969+
970+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
971+ expect ( panel . getAttribute ( 'aria-label' ) ) . toBe ( 'My label' ) ;
972+ expect ( panel . hasAttribute ( 'aria-labelledby' ) ) . toBe ( false ) ;
973+ } ) ) ;
974+
932975 } ) ;
933976
934977 describe ( 'for options' , ( ) => {
@@ -2210,49 +2253,7 @@ describe('MatSelect', () => {
22102253 options = overlayContainerElement . querySelectorAll ( 'mat-option' ) as NodeListOf < HTMLElement > ;
22112254 } ) ) ;
22122255
2213- it ( 'should set aria-owns properly' , fakeAsync ( ( ) => {
2214- const selects = fixture . debugElement . queryAll ( By . css ( 'mat-select' ) ) ;
2215-
2216- expect ( selects [ 0 ] . nativeElement . getAttribute ( 'aria-owns' ) )
2217- . toContain ( options [ 0 ] . id , `Expected aria-owns to contain IDs of its child options.` ) ;
2218- expect ( selects [ 0 ] . nativeElement . getAttribute ( 'aria-owns' ) )
2219- . toContain ( options [ 1 ] . id , `Expected aria-owns to contain IDs of its child options.` ) ;
2220-
2221- const backdrop =
2222- overlayContainerElement . querySelector ( '.cdk-overlay-backdrop' ) as HTMLElement ;
2223- backdrop . click ( ) ;
2224- fixture . detectChanges ( ) ;
2225- flush ( ) ;
2226-
2227- triggers [ 1 ] . nativeElement . click ( ) ;
2228- fixture . detectChanges ( ) ;
2229- flush ( ) ;
2230-
2231- options =
2232- overlayContainerElement . querySelectorAll ( 'mat-option' ) as NodeListOf < HTMLElement > ;
2233- expect ( selects [ 1 ] . nativeElement . getAttribute ( 'aria-owns' ) )
2234- . toContain ( options [ 0 ] . id , `Expected aria-owns to contain IDs of its child options.` ) ;
2235- expect ( selects [ 1 ] . nativeElement . getAttribute ( 'aria-owns' ) )
2236- . toContain ( options [ 1 ] . id , `Expected aria-owns to contain IDs of its child options.` ) ;
2237- } ) ) ;
2238-
2239- it ( 'should remove aria-owns when the options are not visible' , fakeAsync ( ( ) => {
2240- const select = fixture . debugElement . query ( By . css ( 'mat-select' ) ) ! ;
2241-
2242- expect ( select . nativeElement . hasAttribute ( 'aria-owns' ) )
2243- . toBe ( true , 'Expected select to have aria-owns while open.' ) ;
2244-
2245- const backdrop =
2246- overlayContainerElement . querySelector ( '.cdk-overlay-backdrop' ) as HTMLElement ;
2247- backdrop . click ( ) ;
2248- fixture . detectChanges ( ) ;
2249- flush ( ) ;
2250-
2251- expect ( select . nativeElement . hasAttribute ( 'aria-owns' ) )
2252- . toBe ( false , 'Expected select not to have aria-owns when closed.' ) ;
2253- } ) ) ;
2254-
2255- it ( 'should set the option id properly' , fakeAsync ( ( ) => {
2256+ it ( 'should set the option id' , fakeAsync ( ( ) => {
22562257 let firstOptionID = options [ 0 ] . id ;
22572258
22582259 expect ( options [ 0 ] . id )
0 commit comments