@@ -145,32 +145,58 @@ 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 ( 'true' ) ;
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+ expect ( select . hasAttribute ( 'aria-controls' ) ) . toBe ( false ) ;
156+
157+ fixture . componentInstance . select . open ( ) ;
158+ fixture . detectChanges ( ) ;
159+ flush ( ) ;
160+
161+ const ariaControls = select . getAttribute ( 'aria-controls' ) ;
162+ expect ( ariaControls ) . toBeTruthy ( ) ;
163+ expect ( ariaControls ) . toBe ( document . querySelector ( '.mat-select-panel' ) ! . id ) ;
164+ } ) ) ;
165+
166+ it ( 'should set aria-expanded based on the select open state' , fakeAsync ( ( ) => {
167+ expect ( select . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' ) ;
168+
169+ fixture . componentInstance . select . open ( ) ;
170+ fixture . detectChanges ( ) ;
171+ flush ( ) ;
172+
173+ expect ( select . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' ) ;
154174 } ) ) ;
155175
156176 it ( 'should support setting a custom aria-label' , fakeAsync ( ( ) => {
157177 fixture . componentInstance . ariaLabel = 'Custom Label' ;
158178 fixture . detectChanges ( ) ;
159179
160180 expect ( select . getAttribute ( 'aria-label' ) ) . toEqual ( 'Custom Label' ) ;
181+ expect ( select . hasAttribute ( 'aria-labelledby' ) ) . toBeFalsy ( ) ;
161182 } ) ) ;
162183
163- it ( 'should not set an aria-label if aria-labelledby is specified ' , fakeAsync ( ( ) => {
184+ it ( 'should be able to add an extra aria-labelledby on top of the default ' , fakeAsync ( ( ) => {
164185 fixture . componentInstance . ariaLabelledby = 'myLabelId' ;
165186 fixture . detectChanges ( ) ;
166187
167- expect ( select . getAttribute ( 'aria-label' ) ) . toBeFalsy ( 'Expected no aria-label to be set.' ) ;
168- expect ( select . getAttribute ( 'aria-labelledby' ) ) . toBe ( 'myLabelId' ) ;
188+ const labelId = fixture . nativeElement . querySelector ( '.mat-form-field-label' ) . id ;
189+ const valueId = fixture . nativeElement . querySelector ( '.mat-select-value' ) . id ;
190+
191+ expect ( select . getAttribute ( 'aria-labelledby' ) ) . toBe ( `${ labelId } ${ valueId } myLabelId` ) ;
169192 } ) ) ;
170193
171- it ( 'should not have aria-labelledby in the DOM if it`s not specified ' , fakeAsync ( ( ) => {
194+ it ( 'should set aria-labelledby to the value and label IDs ' , fakeAsync ( ( ) => {
172195 fixture . detectChanges ( ) ;
173- expect ( select . hasAttribute ( 'aria-labelledby' ) ) . toBeFalsy ( ) ;
196+
197+ const labelId = fixture . nativeElement . querySelector ( '.mat-form-field-label' ) . id ;
198+ const valueId = fixture . nativeElement . querySelector ( '.mat-select-value' ) . id ;
199+ expect ( select . getAttribute ( 'aria-labelledby' ) ) . toBe ( `${ labelId } ${ valueId } ` ) ;
174200 } ) ) ;
175201
176202 it ( 'should set the tabindex of the select to 0 by default' , fakeAsync ( ( ) => {
@@ -237,37 +263,15 @@ describe('MatSelect', () => {
237263 expect ( select . getAttribute ( 'tabindex' ) ) . toEqual ( '0' ) ;
238264 } ) ) ;
239265
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' , ( ) => {
266+ it ( 'should set `aria-labelledby` to the value ID if there is no form field' , ( ) => {
264267 fixture . destroy ( ) ;
265268
266269 const labelFixture = TestBed . createComponent ( SelectWithChangeEvent ) ;
267270 labelFixture . detectChanges ( ) ;
268271 select = labelFixture . debugElement . query ( By . css ( 'mat-select' ) ) ! . nativeElement ;
272+ const valueId = labelFixture . nativeElement . querySelector ( '.mat-select-value' ) . id ;
269273
270- expect ( select . getAttribute ( 'aria-labelledby' ) ) . toBeFalsy ( ) ;
274+ expect ( select . getAttribute ( 'aria-labelledby' ) ?. trim ( ) ) . toBe ( valueId ) ;
271275 } ) ;
272276
273277 it ( 'should select options via the UP/DOWN arrow keys on a closed select' , fakeAsync ( ( ) => {
@@ -812,28 +816,28 @@ describe('MatSelect', () => {
812816 expect ( document . activeElement ) . toBe ( select , 'Expected select element to be focused.' ) ;
813817 } ) ) ;
814818
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 ) ;
819+ it ( 'should set `aria-multiselectable` to true on the listbox inside multi select' ,
820+ fakeAsync ( ( ) => {
821+ fixture . destroy ( ) ;
828822
829- multiFixture . detectChanges ( ) ;
830- select = multiFixture . debugElement . query ( By . css ( 'mat-select' ) ) ! . nativeElement ;
823+ const multiFixture = TestBed . createComponent ( MultiSelect ) ;
824+ multiFixture . detectChanges ( ) ;
825+ select = multiFixture . debugElement . query ( By . css ( 'mat-select' ) ) ! . nativeElement ;
826+ multiFixture . componentInstance . select . open ( ) ;
827+ multiFixture . detectChanges ( ) ;
828+ flush ( ) ;
831829
832- expect ( select . getAttribute ( 'aria-multiselectable' ) ) . toBe ( 'true' ) ;
833- } ) ) ;
830+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
831+ expect ( panel . getAttribute ( 'aria-multiselectable' ) ) . toBe ( 'true' ) ;
832+ } ) ) ;
834833
835834 it ( 'should set aria-multiselectable false on single-selection instances' , fakeAsync ( ( ) => {
836- expect ( select . getAttribute ( 'aria-multiselectable' ) ) . toBe ( 'false' ) ;
835+ fixture . componentInstance . select . open ( ) ;
836+ fixture . detectChanges ( ) ;
837+ flush ( ) ;
838+
839+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
840+ expect ( panel . getAttribute ( 'aria-multiselectable' ) ) . toBe ( 'false' ) ;
837841 } ) ) ;
838842
839843 it ( 'should set aria-activedescendant only while the panel is open' , fakeAsync ( ( ) => {
@@ -929,6 +933,47 @@ describe('MatSelect', () => {
929933 expect ( document . activeElement ) . toBe ( select , 'Expected trigger to be focused.' ) ;
930934 } ) ) ;
931935
936+ it ( 'should set a role of listbox on the select panel' , fakeAsync ( ( ) => {
937+ fixture . componentInstance . select . open ( ) ;
938+ fixture . detectChanges ( ) ;
939+ flush ( ) ;
940+
941+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
942+ expect ( panel . getAttribute ( 'role' ) ) . toBe ( 'listbox' ) ;
943+ } ) ) ;
944+
945+ it ( 'should point the aria-labelledby of the panel to the field label' , fakeAsync ( ( ) => {
946+ fixture . componentInstance . select . open ( ) ;
947+ fixture . detectChanges ( ) ;
948+ flush ( ) ;
949+
950+ const labelId = fixture . nativeElement . querySelector ( '.mat-form-field-label' ) . id ;
951+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
952+ expect ( panel . getAttribute ( 'aria-labelledby' ) ) . toBe ( labelId ) ;
953+ } ) ) ;
954+
955+ it ( 'should add a custom aria-labelledby to the panel' , fakeAsync ( ( ) => {
956+ fixture . componentInstance . ariaLabelledby = 'myLabelId' ;
957+ fixture . componentInstance . select . open ( ) ;
958+ fixture . detectChanges ( ) ;
959+ flush ( ) ;
960+
961+ const labelId = fixture . nativeElement . querySelector ( '.mat-form-field-label' ) . id ;
962+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
963+ expect ( panel . getAttribute ( 'aria-labelledby' ) ) . toBe ( `${ labelId } myLabelId` ) ;
964+ } ) ) ;
965+
966+ it ( 'should clear aria-labelledby from the panel if an aria-label is set' , fakeAsync ( ( ) => {
967+ fixture . componentInstance . ariaLabel = 'My label' ;
968+ fixture . componentInstance . select . open ( ) ;
969+ fixture . detectChanges ( ) ;
970+ flush ( ) ;
971+
972+ const panel = document . querySelector ( '.mat-select-panel' ) ! ;
973+ expect ( panel . getAttribute ( 'aria-label' ) ) . toBe ( 'My label' ) ;
974+ expect ( panel . hasAttribute ( 'aria-labelledby' ) ) . toBe ( false ) ;
975+ } ) ) ;
976+
932977 } ) ;
933978
934979 describe ( 'for options' , ( ) => {
@@ -2223,49 +2268,7 @@ describe('MatSelect', () => {
22232268 options = overlayContainerElement . querySelectorAll ( 'mat-option' ) as NodeListOf < HTMLElement > ;
22242269 } ) ) ;
22252270
2226- it ( 'should set aria-owns properly' , fakeAsync ( ( ) => {
2227- const selects = fixture . debugElement . queryAll ( By . css ( 'mat-select' ) ) ;
2228-
2229- expect ( selects [ 0 ] . nativeElement . getAttribute ( 'aria-owns' ) )
2230- . toContain ( options [ 0 ] . id , `Expected aria-owns to contain IDs of its child options.` ) ;
2231- expect ( selects [ 0 ] . nativeElement . getAttribute ( 'aria-owns' ) )
2232- . toContain ( options [ 1 ] . id , `Expected aria-owns to contain IDs of its child options.` ) ;
2233-
2234- const backdrop =
2235- overlayContainerElement . querySelector ( '.cdk-overlay-backdrop' ) as HTMLElement ;
2236- backdrop . click ( ) ;
2237- fixture . detectChanges ( ) ;
2238- flush ( ) ;
2239-
2240- triggers [ 1 ] . nativeElement . click ( ) ;
2241- fixture . detectChanges ( ) ;
2242- flush ( ) ;
2243-
2244- options =
2245- overlayContainerElement . querySelectorAll ( 'mat-option' ) as NodeListOf < HTMLElement > ;
2246- expect ( selects [ 1 ] . nativeElement . getAttribute ( 'aria-owns' ) )
2247- . toContain ( options [ 0 ] . id , `Expected aria-owns to contain IDs of its child options.` ) ;
2248- expect ( selects [ 1 ] . nativeElement . getAttribute ( 'aria-owns' ) )
2249- . toContain ( options [ 1 ] . id , `Expected aria-owns to contain IDs of its child options.` ) ;
2250- } ) ) ;
2251-
2252- it ( 'should remove aria-owns when the options are not visible' , fakeAsync ( ( ) => {
2253- const select = fixture . debugElement . query ( By . css ( 'mat-select' ) ) ! ;
2254-
2255- expect ( select . nativeElement . hasAttribute ( 'aria-owns' ) )
2256- . toBe ( true , 'Expected select to have aria-owns while open.' ) ;
2257-
2258- const backdrop =
2259- overlayContainerElement . querySelector ( '.cdk-overlay-backdrop' ) as HTMLElement ;
2260- backdrop . click ( ) ;
2261- fixture . detectChanges ( ) ;
2262- flush ( ) ;
2263-
2264- expect ( select . nativeElement . hasAttribute ( 'aria-owns' ) )
2265- . toBe ( false , 'Expected select not to have aria-owns when closed.' ) ;
2266- } ) ) ;
2267-
2268- it ( 'should set the option id properly' , fakeAsync ( ( ) => {
2271+ it ( 'should set the option id' , fakeAsync ( ( ) => {
22692272 let firstOptionID = options [ 0 ] . id ;
22702273
22712274 expect ( options [ 0 ] . id )
@@ -4590,6 +4593,13 @@ describe('MatSelect', () => {
45904593 expect ( select . disableOptionCentering ) . toBe ( true ) ;
45914594 expect ( select . typeaheadDebounceInterval ) . toBe ( 1337 ) ;
45924595 } ) ;
4596+
4597+ it ( 'should not not throw if the select is inside an ng-container with ngIf' , fakeAsync ( ( ) => {
4598+ configureMatSelectTestingModule ( [ SelectInNgContainer ] ) ;
4599+ const fixture = TestBed . createComponent ( SelectInNgContainer ) ;
4600+ expect ( ( ) => fixture . detectChanges ( ) ) . not . toThrow ( ) ;
4601+ } ) ) ;
4602+
45934603} ) ;
45944604
45954605
@@ -5430,3 +5440,20 @@ class SelectWithResetOptionAndFormControl {
54305440 @ViewChildren ( MatOption ) options : QueryList < MatOption > ;
54315441 control = new FormControl ( ) ;
54325442}
5443+
5444+
5445+ @Component ( {
5446+ selector : 'select-with-placeholder-in-ngcontainer-with-ngIf' ,
5447+ template : `
5448+ <mat-form-field>
5449+ <ng-container *ngIf="true">
5450+ <mat-select placeholder="Product Area">
5451+ <mat-option value="a">A</mat-option>
5452+ <mat-option value="b">B</mat-option>
5453+ <mat-option value="c">C</mat-option>
5454+ </mat-select>
5455+ </ng-container>
5456+ </mat-form-field>
5457+ `
5458+ } )
5459+ class SelectInNgContainer { }
0 commit comments