1- import { async , TestBed } from '@angular/core/testing' ;
2- import { Component , ViewChild } from '@angular/core' ;
1+ import { async , TestBed , inject } from '@angular/core/testing' ;
2+ import { Component , ViewChild , QueryList , ViewChildren } from '@angular/core' ;
33import { By } from '@angular/platform-browser' ;
44import { BrowserAnimationsModule } from '@angular/platform-browser/animations' ;
5- import { MatExpansionModule , MatAccordion , MatExpansionPanel } from './index' ;
5+ import {
6+ MatExpansionModule ,
7+ MatAccordion ,
8+ MatExpansionPanel ,
9+ MatExpansionPanelHeader ,
10+ } from './index' ;
11+ import { dispatchKeyboardEvent } from '@angular/cdk/testing' ;
12+ import { DOWN_ARROW , UP_ARROW , HOME , END } from '@angular/cdk/keycodes' ;
13+ import { FocusMonitor } from '@angular/cdk/a11y' ;
614
715
816describe ( 'MatAccordion' , ( ) => {
17+ let focusMonitor : FocusMonitor ;
18+
919 beforeEach ( async ( ( ) => {
1020 TestBed . configureTestingModule ( {
1121 imports : [
@@ -19,41 +29,53 @@ describe('MatAccordion', () => {
1929 ] ,
2030 } ) ;
2131 TestBed . compileComponents ( ) ;
32+
33+ inject ( [ FocusMonitor ] , ( fm : FocusMonitor ) => {
34+ focusMonitor = fm ;
35+ } ) ( ) ;
2236 } ) ) ;
2337
2438 it ( 'should ensure only one item is expanded at a time' , ( ) => {
2539 const fixture = TestBed . createComponent ( SetOfItems ) ;
40+ fixture . detectChanges ( ) ;
41+
2642 const items = fixture . debugElement . queryAll ( By . css ( '.mat-expansion-panel' ) ) ;
43+ const panelInstances = fixture . componentInstance . panels . toArray ( ) ;
2744
28- fixture . componentInstance . firstPanelExpanded = true ;
45+ panelInstances [ 0 ] . expanded = true ;
2946 fixture . detectChanges ( ) ;
3047 expect ( items [ 0 ] . classes [ 'mat-expanded' ] ) . toBeTruthy ( ) ;
3148 expect ( items [ 1 ] . classes [ 'mat-expanded' ] ) . toBeFalsy ( ) ;
3249
33- fixture . componentInstance . secondPanelExpanded = true ;
50+ panelInstances [ 1 ] . expanded = true ;
3451 fixture . detectChanges ( ) ;
3552 expect ( items [ 0 ] . classes [ 'mat-expanded' ] ) . toBeFalsy ( ) ;
3653 expect ( items [ 1 ] . classes [ 'mat-expanded' ] ) . toBeTruthy ( ) ;
3754 } ) ;
3855
3956 it ( 'should allow multiple items to be expanded simultaneously' , ( ) => {
4057 const fixture = TestBed . createComponent ( SetOfItems ) ;
58+ fixture . componentInstance . multi = true ;
59+ fixture . detectChanges ( ) ;
60+
4161 const panels = fixture . debugElement . queryAll ( By . css ( '.mat-expansion-panel' ) ) ;
62+ const panelInstances = fixture . componentInstance . panels . toArray ( ) ;
4263
43- fixture . componentInstance . multi = true ;
44- fixture . componentInstance . firstPanelExpanded = true ;
45- fixture . componentInstance . secondPanelExpanded = true ;
64+ panelInstances [ 0 ] . expanded = true ;
65+ panelInstances [ 1 ] . expanded = true ;
4666 fixture . detectChanges ( ) ;
4767 expect ( panels [ 0 ] . classes [ 'mat-expanded' ] ) . toBeTruthy ( ) ;
4868 expect ( panels [ 1 ] . classes [ 'mat-expanded' ] ) . toBeTruthy ( ) ;
4969 } ) ;
5070
5171 it ( 'should expand or collapse all enabled items' , ( ) => {
5272 const fixture = TestBed . createComponent ( SetOfItems ) ;
73+ fixture . detectChanges ( ) ;
74+
5375 const panels = fixture . debugElement . queryAll ( By . css ( '.mat-expansion-panel' ) ) ;
5476
5577 fixture . componentInstance . multi = true ;
56- fixture . componentInstance . secondPanelExpanded = true ;
78+ fixture . componentInstance . panels . toArray ( ) [ 1 ] . expanded = true ;
5779 fixture . detectChanges ( ) ;
5880 expect ( panels [ 0 ] . classes [ 'mat-expanded' ] ) . toBeFalsy ( ) ;
5981 expect ( panels [ 1 ] . classes [ 'mat-expanded' ] ) . toBeTruthy ( ) ;
@@ -71,10 +93,12 @@ describe('MatAccordion', () => {
7193
7294 it ( 'should not expand or collapse disabled items' , ( ) => {
7395 const fixture = TestBed . createComponent ( SetOfItems ) ;
96+ fixture . detectChanges ( ) ;
97+
7498 const panels = fixture . debugElement . queryAll ( By . css ( '.mat-expansion-panel' ) ) ;
7599
76100 fixture . componentInstance . multi = true ;
77- fixture . componentInstance . secondPanelDisabled = true ;
101+ fixture . componentInstance . panels . toArray ( ) [ 1 ] . disabled = true ;
78102 fixture . detectChanges ( ) ;
79103 fixture . componentInstance . accordion . openAll ( ) ;
80104 fixture . detectChanges ( ) ;
@@ -110,27 +134,107 @@ describe('MatAccordion', () => {
110134 expect ( panel . nativeElement . querySelector ( '.mat-expansion-indicator' ) )
111135 . toBeFalsy ( 'Expected the expansion indicator to be removed.' ) ;
112136 } ) ;
137+
138+ it ( 'should move focus to the next header when pressing the down arrow' , ( ) => {
139+ const fixture = TestBed . createComponent ( SetOfItems ) ;
140+ fixture . detectChanges ( ) ;
141+
142+ const headerElements = fixture . debugElement . queryAll ( By . css ( 'mat-expansion-panel-header' ) ) ;
143+ const headers = fixture . componentInstance . headers . toArray ( ) ;
144+
145+ focusMonitor . focusVia ( headerElements [ 0 ] . nativeElement , 'keyboard' ) ;
146+ headers . forEach ( header => spyOn ( header , 'focus' ) ) ;
147+
148+ // Stop at the second-last header so focus doesn't wrap around.
149+ for ( let i = 0 ; i < headerElements . length - 1 ; i ++ ) {
150+ dispatchKeyboardEvent ( headerElements [ i ] . nativeElement , 'keydown' , DOWN_ARROW ) ;
151+ fixture . detectChanges ( ) ;
152+ expect ( headers [ i + 1 ] . focus ) . toHaveBeenCalledTimes ( 1 ) ;
153+ }
154+ } ) ;
155+
156+ it ( 'should move focus to the next header when pressing the up arrow' , ( ) => {
157+ const fixture = TestBed . createComponent ( SetOfItems ) ;
158+ fixture . detectChanges ( ) ;
159+
160+ const headerElements = fixture . debugElement . queryAll ( By . css ( 'mat-expansion-panel-header' ) ) ;
161+ const headers = fixture . componentInstance . headers . toArray ( ) ;
162+
163+ focusMonitor . focusVia ( headerElements [ headerElements . length - 1 ] . nativeElement , 'keyboard' ) ;
164+ headers . forEach ( header => spyOn ( header , 'focus' ) ) ;
165+
166+ // Stop before the first header
167+ for ( let i = headers . length - 1 ; i > 0 ; i -- ) {
168+ dispatchKeyboardEvent ( headerElements [ i ] . nativeElement , 'keydown' , UP_ARROW ) ;
169+ fixture . detectChanges ( ) ;
170+ expect ( headers [ i - 1 ] . focus ) . toHaveBeenCalledTimes ( 1 ) ;
171+ }
172+ } ) ;
173+
174+ it ( 'should skip disabled items when moving focus with the keyboard' , ( ) => {
175+ const fixture = TestBed . createComponent ( SetOfItems ) ;
176+ fixture . detectChanges ( ) ;
177+
178+ const headerElements = fixture . debugElement . queryAll ( By . css ( 'mat-expansion-panel-header' ) ) ;
179+ const panels = fixture . componentInstance . panels . toArray ( ) ;
180+ const headers = fixture . componentInstance . headers . toArray ( ) ;
181+
182+ focusMonitor . focusVia ( headerElements [ 0 ] . nativeElement , 'keyboard' ) ;
183+ headers . forEach ( header => spyOn ( header , 'focus' ) ) ;
184+ panels [ 1 ] . disabled = true ;
185+ fixture . detectChanges ( ) ;
186+
187+ dispatchKeyboardEvent ( headerElements [ 0 ] . nativeElement , 'keydown' , DOWN_ARROW ) ;
188+ fixture . detectChanges ( ) ;
189+
190+ expect ( headers [ 1 ] . focus ) . not . toHaveBeenCalled ( ) ;
191+ expect ( headers [ 2 ] . focus ) . toHaveBeenCalledTimes ( 1 ) ;
192+ } ) ;
193+
194+ it ( 'should focus the first header when pressing the home key' , ( ) => {
195+ const fixture = TestBed . createComponent ( SetOfItems ) ;
196+ fixture . detectChanges ( ) ;
197+
198+ const headerElements = fixture . debugElement . queryAll ( By . css ( 'mat-expansion-panel-header' ) ) ;
199+ const headers = fixture . componentInstance . headers . toArray ( ) ;
200+
201+ headers . forEach ( header => spyOn ( header , 'focus' ) ) ;
202+ dispatchKeyboardEvent ( headerElements [ headerElements . length - 1 ] . nativeElement , 'keydown' , HOME ) ;
203+ fixture . detectChanges ( ) ;
204+
205+ expect ( headers [ 0 ] . focus ) . toHaveBeenCalledTimes ( 1 ) ;
206+ } ) ;
207+
208+ it ( 'should focus the last header when pressing the end key' , ( ) => {
209+ const fixture = TestBed . createComponent ( SetOfItems ) ;
210+ fixture . detectChanges ( ) ;
211+
212+ const headerElements = fixture . debugElement . queryAll ( By . css ( 'mat-expansion-panel-header' ) ) ;
213+ const headers = fixture . componentInstance . headers . toArray ( ) ;
214+
215+ headers . forEach ( header => spyOn ( header , 'focus' ) ) ;
216+ dispatchKeyboardEvent ( headerElements [ 0 ] . nativeElement , 'keydown' , END ) ;
217+ fixture . detectChanges ( ) ;
218+
219+ expect ( headers [ headers . length - 1 ] . focus ) . toHaveBeenCalledTimes ( 1 ) ;
220+ } ) ;
221+
113222} ) ;
114223
115224
116225@Component ( { template : `
117226 <mat-accordion [multi]="multi">
118- <mat-expansion-panel [expanded]="firstPanelExpanded">
119- <mat-expansion-panel-header>Summary</mat-expansion-panel-header>
120- <p>Content</p>
121- </mat-expansion-panel>
122- <mat-expansion-panel [expanded]="secondPanelExpanded" [disabled]="secondPanelDisabled">
123- <mat-expansion-panel-header>Summary</mat-expansion-panel-header>
227+ <mat-expansion-panel *ngFor="let i of [0, 1, 2, 3]">
228+ <mat-expansion-panel-header>Summary {{i}}</mat-expansion-panel-header>
124229 <p>Content</p>
125230 </mat-expansion-panel>
126231 </mat-accordion>` } )
127232class SetOfItems {
128233 @ViewChild ( MatAccordion ) accordion : MatAccordion ;
234+ @ViewChildren ( MatExpansionPanel ) panels : QueryList < MatExpansionPanel > ;
235+ @ViewChildren ( MatExpansionPanelHeader ) headers : QueryList < MatExpansionPanelHeader > ;
129236
130237 multi : boolean = false ;
131- firstPanelExpanded : boolean = false ;
132- secondPanelExpanded : boolean = false ;
133- secondPanelDisabled : boolean = false ;
134238}
135239
136240@Component ( { template : `
0 commit comments