@@ -11,6 +11,8 @@ import {
11
11
UP_ARROW ,
12
12
A ,
13
13
ESCAPE ,
14
+ PAGE_DOWN ,
15
+ PAGE_UP ,
14
16
} from '@angular/cdk/keycodes' ;
15
17
import { OverlayContainer } from '@angular/cdk/overlay' ;
16
18
import { ScrollDispatcher } from '@angular/cdk/scrolling' ;
@@ -419,6 +421,39 @@ describe('MDC-based MatSelect', () => {
419
421
flush ( ) ;
420
422
} ) ) ;
421
423
424
+ it ( 'should select first/last options via the PAGE_DOWN/PAGE_UP keys on a closed select with less than 10 options' , fakeAsync ( ( ) => {
425
+ const formControl = fixture . componentInstance . control ;
426
+ const firstOption = fixture . componentInstance . options . first ;
427
+ const lastOption = fixture . componentInstance . options . last ;
428
+
429
+ expect ( formControl . value ) . withContext ( 'Expected no initial value.' ) . toBeFalsy ( ) ;
430
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( - 1 ) ;
431
+
432
+ const endEvent = dispatchKeyboardEvent ( select , 'keydown' , PAGE_DOWN ) ;
433
+
434
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 7 ) ;
435
+ expect ( endEvent . defaultPrevented ) . toBe ( true ) ;
436
+ expect ( lastOption . selected )
437
+ . withContext ( 'Expected last option to be selected.' )
438
+ . toBe ( true ) ;
439
+ expect ( formControl . value )
440
+ . withContext ( 'Expected value from last option to have been set on the model.' )
441
+ . toBe ( lastOption . value ) ;
442
+
443
+ const homeEvent = dispatchKeyboardEvent ( select , 'keydown' , PAGE_UP ) ;
444
+
445
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 0 ) ;
446
+ expect ( homeEvent . defaultPrevented ) . toBe ( true ) ;
447
+ expect ( firstOption . selected )
448
+ . withContext ( 'Expected first option to be selected.' )
449
+ . toBe ( true ) ;
450
+ expect ( formControl . value )
451
+ . withContext ( 'Expected value from first option to have been set on the model.' )
452
+ . toBe ( firstOption . value ) ;
453
+
454
+ flush ( ) ;
455
+ } ) ) ;
456
+
422
457
it ( 'should resume focus from selected item after selecting via click' , fakeAsync ( ( ) => {
423
458
const formControl = fixture . componentInstance . control ;
424
459
const options = fixture . componentInstance . options . toArray ( ) ;
@@ -1490,6 +1525,37 @@ describe('MDC-based MatSelect', () => {
1490
1525
expect ( event . defaultPrevented ) . toBe ( true ) ;
1491
1526
} ) ) ;
1492
1527
1528
+ it ( 'should focus the last option when pressing PAGE_DOWN with less than 10 options' , fakeAsync ( ( ) => {
1529
+ fixture . componentInstance . control . setValue ( 'pizza-1' ) ;
1530
+ fixture . detectChanges ( ) ;
1531
+
1532
+ trigger . click ( ) ;
1533
+ fixture . detectChanges ( ) ;
1534
+ flush ( ) ;
1535
+
1536
+ const event = dispatchKeyboardEvent ( trigger , 'keydown' , PAGE_DOWN ) ;
1537
+ fixture . detectChanges ( ) ;
1538
+
1539
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 7 ) ;
1540
+ expect ( event . defaultPrevented ) . toBe ( true ) ;
1541
+ } ) ) ;
1542
+
1543
+ it ( 'should focus the first option when pressing PAGE_UP with index < 10' , fakeAsync ( ( ) => {
1544
+ fixture . componentInstance . control . setValue ( 'pizza-1' ) ;
1545
+ fixture . detectChanges ( ) ;
1546
+
1547
+ trigger . click ( ) ;
1548
+ fixture . detectChanges ( ) ;
1549
+ flush ( ) ;
1550
+
1551
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBeLessThan ( 10 ) ;
1552
+ const event = dispatchKeyboardEvent ( trigger , 'keydown' , PAGE_UP ) ;
1553
+ fixture . detectChanges ( ) ;
1554
+
1555
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 0 ) ;
1556
+ expect ( event . defaultPrevented ) . toBe ( true ) ;
1557
+ } ) ) ;
1558
+
1493
1559
it ( 'should be able to set extra classes on the panel' , fakeAsync ( ( ) => {
1494
1560
trigger . click ( ) ;
1495
1561
fixture . detectChanges ( ) ;
@@ -2347,6 +2413,66 @@ describe('MDC-based MatSelect', () => {
2347
2413
. toBe ( 1173 ) ;
2348
2414
} ) ) ;
2349
2415
2416
+ it ( 'should scroll 10 to the top or to first element when pressing PAGE_UP' , fakeAsync ( ( ) => {
2417
+ for ( let i = 0 ; i < 18 ; i ++ ) {
2418
+ dispatchKeyboardEvent ( host , 'keydown' , DOWN_ARROW ) ;
2419
+ fixture . detectChanges ( ) ;
2420
+ }
2421
+
2422
+ expect ( panel . scrollTop )
2423
+ . withContext ( 'Expected panel to be scrolled down.' )
2424
+ . toBeGreaterThan ( 0 ) ;
2425
+
2426
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 18 ) ;
2427
+
2428
+ dispatchKeyboardEvent ( host , 'keydown' , PAGE_UP ) ;
2429
+ fixture . detectChanges ( ) ;
2430
+
2431
+ // <top padding> + <option amount> * <option height>
2432
+ // 8 + 8 × 48
2433
+ expect ( panel . scrollTop ) . withContext ( 'Expected panel to be scrolled to the top' ) . toBe ( 392 ) ;
2434
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 8 ) ;
2435
+
2436
+ dispatchKeyboardEvent ( host , 'keydown' , PAGE_UP ) ;
2437
+ fixture . detectChanges ( ) ;
2438
+
2439
+ // 8px is the top padding of the panel.
2440
+ expect ( panel . scrollTop ) . withContext ( 'Expected panel to be scrolled to the top' ) . toBe ( 8 ) ;
2441
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 0 ) ;
2442
+ } ) ) ;
2443
+
2444
+ it ( 'should scroll 10 to the bottom of the panel when pressing PAGE_DOWN' , fakeAsync ( ( ) => {
2445
+ dispatchKeyboardEvent ( host , 'keydown' , PAGE_DOWN ) ;
2446
+ fixture . detectChanges ( ) ;
2447
+
2448
+ // <top padding> + <option amount> * <option height> - <panel height> =
2449
+ // 8 + 11 * 48 - 275 = 261
2450
+ expect ( panel . scrollTop )
2451
+ . withContext ( 'Expected panel to be scrolled 10 to the bottom' )
2452
+ . toBe ( 261 ) ;
2453
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 10 ) ;
2454
+
2455
+ dispatchKeyboardEvent ( host , 'keydown' , PAGE_DOWN ) ;
2456
+ fixture . detectChanges ( ) ;
2457
+
2458
+ // <top padding> + <option amount> * <option height> - <panel height> =
2459
+ // 8 + 21 * 48 - 275 = 741
2460
+ expect ( panel . scrollTop )
2461
+ . withContext ( 'Expected panel to be scrolled 10 to the bottom' )
2462
+ . toBe ( 741 ) ;
2463
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 20 ) ;
2464
+
2465
+ dispatchKeyboardEvent ( host , 'keydown' , PAGE_DOWN ) ;
2466
+ fixture . detectChanges ( ) ;
2467
+
2468
+ // <top padding> + <option amount> * <option height> - <panel height> =
2469
+ // 8 + 30 * 48 - 275 = 1173
2470
+ expect ( panel . scrollTop )
2471
+ . withContext ( 'Expected panel to be scrolled 10 to the bottom' )
2472
+ . toBe ( 1173 ) ;
2473
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 29 ) ;
2474
+ } ) ) ;
2475
+
2350
2476
it ( 'should scroll to the active option when typing' , fakeAsync ( ( ) => {
2351
2477
for ( let i = 0 ; i < 15 ; i ++ ) {
2352
2478
// Press the letter 'o' 15 times since all the options are named 'Option <index>'
@@ -4209,6 +4335,51 @@ describe('MDC-based MatSelect', () => {
4209
4335
const fixture = TestBed . createComponent ( SelectInNgContainer ) ;
4210
4336
expect ( ( ) => fixture . detectChanges ( ) ) . not . toThrow ( ) ;
4211
4337
} ) ) ;
4338
+ describe ( 'page up/down with disabled options' , ( ) => {
4339
+ let fixture : ComponentFixture < BasicSelectWithFirstAndLastOptionDisabled > ;
4340
+ let host : HTMLElement ;
4341
+
4342
+ beforeEach ( waitForAsync ( ( ) =>
4343
+ configureMatSelectTestingModule ( [ BasicSelectWithFirstAndLastOptionDisabled ] ) ) ) ;
4344
+
4345
+ beforeEach ( fakeAsync ( ( ) => {
4346
+ fixture = TestBed . createComponent ( BasicSelectWithFirstAndLastOptionDisabled ) ;
4347
+
4348
+ fixture . detectChanges ( ) ;
4349
+ fixture . componentInstance . select . open ( ) ;
4350
+ fixture . detectChanges ( ) ;
4351
+ flush ( ) ;
4352
+ fixture . detectChanges ( ) ;
4353
+
4354
+ host = fixture . debugElement . query ( By . css ( 'mat-select' ) ) ! . nativeElement ;
4355
+ } ) ) ;
4356
+
4357
+ it ( 'should scroll to the second one pressing PAGE_UP, because the first one is disabled' , fakeAsync ( ( ) => {
4358
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 1 ) ;
4359
+
4360
+ dispatchKeyboardEvent ( host , 'keydown' , PAGE_UP ) ;
4361
+ fixture . detectChanges ( ) ;
4362
+
4363
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 1 ) ;
4364
+
4365
+ dispatchKeyboardEvent ( host , 'keydown' , PAGE_UP ) ;
4366
+ fixture . detectChanges ( ) ;
4367
+
4368
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 1 ) ;
4369
+ } ) ) ;
4370
+
4371
+ it ( 'should scroll by PAGE_DOWN to the one before the last, because last one is disabled' , fakeAsync ( ( ) => {
4372
+ dispatchKeyboardEvent ( host , 'keydown' , PAGE_DOWN ) ;
4373
+ fixture . detectChanges ( ) ;
4374
+
4375
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 6 ) ;
4376
+
4377
+ dispatchKeyboardEvent ( host , 'keydown' , PAGE_DOWN ) ;
4378
+ fixture . detectChanges ( ) ;
4379
+
4380
+ expect ( fixture . componentInstance . select . _keyManager . activeItemIndex ) . toBe ( 6 ) ;
4381
+ } ) ) ;
4382
+ } ) ;
4212
4383
} ) ;
4213
4384
4214
4385
@Component ( {
@@ -5091,3 +5262,51 @@ class SelectInsideDynamicFormGroup {
5091
5262
} ) ;
5092
5263
}
5093
5264
}
5265
+ @Component ( {
5266
+ selector : 'basic-select' ,
5267
+ template : `
5268
+ <div [style.height.px]="heightAbove"></div>
5269
+ <mat-form-field>
5270
+ <mat-label *ngIf="hasLabel">Select a food</mat-label>
5271
+ <mat-select placeholder="Food" [formControl]="control" [required]="isRequired"
5272
+ [tabIndex]="tabIndexOverride" [aria-describedby]="ariaDescribedBy"
5273
+ [aria-label]="ariaLabel" [aria-labelledby]="ariaLabelledby"
5274
+ [panelClass]="panelClass" [disableRipple]="disableRipple"
5275
+ [typeaheadDebounceInterval]="typeaheadDebounceInterval">
5276
+ <mat-option *ngFor="let food of foods" [value]="food.value" [disabled]="food.disabled">
5277
+ {{ food.viewValue }}
5278
+ </mat-option>
5279
+ </mat-select>
5280
+ <mat-hint *ngIf="hint">{{ hint }}</mat-hint>
5281
+ </mat-form-field>
5282
+ <div [style.height.px]="heightBelow"></div>
5283
+ ` ,
5284
+ } )
5285
+ class BasicSelectWithFirstAndLastOptionDisabled {
5286
+ foods : any [ ] = [
5287
+ { value : 'steak-0' , viewValue : 'Steak' , disabled : true } ,
5288
+ { value : 'pizza-1' , viewValue : 'Pizza' } ,
5289
+ { value : 'tacos-2' , viewValue : 'Tacos' } ,
5290
+ { value : 'sandwich-3' , viewValue : 'Sandwich' } ,
5291
+ { value : 'chips-4' , viewValue : 'Chips' } ,
5292
+ { value : 'eggs-5' , viewValue : 'Eggs' } ,
5293
+ { value : 'pasta-6' , viewValue : 'Pasta' } ,
5294
+ { value : 'sushi-7' , viewValue : 'Sushi' , disabled : true } ,
5295
+ ] ;
5296
+ control = new FormControl < string | null > ( null ) ;
5297
+ isRequired : boolean ;
5298
+ heightAbove = 0 ;
5299
+ heightBelow = 0 ;
5300
+ hasLabel = true ;
5301
+ hint : string ;
5302
+ tabIndexOverride : number ;
5303
+ ariaDescribedBy : string ;
5304
+ ariaLabel : string ;
5305
+ ariaLabelledby : string ;
5306
+ panelClass = [ 'custom-one' , 'custom-two' ] ;
5307
+ disableRipple : boolean ;
5308
+ typeaheadDebounceInterval : number ;
5309
+
5310
+ @ViewChild ( MatSelect , { static : true } ) select : MatSelect ;
5311
+ @ViewChildren ( MatOption ) options : QueryList < MatOption > ;
5312
+ }
0 commit comments