@@ -308,21 +308,271 @@ describe('CdkOption', () => {
308
308
expect ( listboxInstance . _listKeyManager . activeItem ) . toEqual ( optionInstances [ 2 ] ) ;
309
309
expect ( listboxInstance . _listKeyManager . activeItemIndex ) . toBe ( 2 ) ;
310
310
} ) ;
311
+
312
+ it ( 'should update selected option on click event' , ( ) => {
313
+ let selectedOptions = optionInstances . filter ( option => option . selected ) ;
314
+
315
+ expect ( selectedOptions . length ) . toBe ( 0 ) ;
316
+ expect ( optionElements [ 0 ] . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
317
+ expect ( optionInstances [ 0 ] . selected ) . toBeFalse ( ) ;
318
+ expect ( fixture . componentInstance . changedOption ) . toBeUndefined ( ) ;
319
+
320
+ dispatchMouseEvent ( optionElements [ 0 ] , 'click' ) ;
321
+ fixture . detectChanges ( ) ;
322
+
323
+ selectedOptions = optionInstances . filter ( option => option . selected ) ;
324
+ expect ( selectedOptions . length ) . toBe ( 1 ) ;
325
+ expect ( optionElements [ 0 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
326
+ expect ( optionInstances [ 0 ] . selected ) . toBeTrue ( ) ;
327
+ expect ( fixture . componentInstance . changedOption ) . toBeDefined ( ) ;
328
+ expect ( fixture . componentInstance . changedOption . id ) . toBe ( optionInstances [ 0 ] . id ) ;
329
+ } ) ;
330
+ } ) ;
331
+
332
+ describe ( 'with multiple selection' , ( ) => {
333
+ let fixture : ComponentFixture < ListboxMultiselect > ;
334
+
335
+ let testComponent : ListboxMultiselect ;
336
+
337
+ let listbox : DebugElement ;
338
+ let listboxInstance : CdkListbox ;
339
+
340
+ let options : DebugElement [ ] ;
341
+ let optionInstances : CdkOption [ ] ;
342
+ let optionElements : HTMLElement [ ] ;
343
+
344
+ beforeEach ( async ( ( ) => {
345
+ TestBed . configureTestingModule ( {
346
+ imports : [ CdkListboxModule ] ,
347
+ declarations : [ ListboxMultiselect ] ,
348
+ } ) . compileComponents ( ) ;
349
+ } ) ) ;
350
+
351
+ beforeEach ( async ( ( ) => {
352
+ fixture = TestBed . createComponent ( ListboxMultiselect ) ;
353
+ fixture . detectChanges ( ) ;
354
+
355
+ testComponent = fixture . debugElement . componentInstance ;
356
+
357
+ listbox = fixture . debugElement . query ( By . directive ( CdkListbox ) ) ;
358
+ listboxInstance = listbox . injector . get < CdkListbox > ( CdkListbox ) ;
359
+
360
+ options = fixture . debugElement . queryAll ( By . directive ( CdkOption ) ) ;
361
+ optionInstances = options . map ( o => o . injector . get < CdkOption > ( CdkOption ) ) ;
362
+ optionElements = options . map ( o => o . nativeElement ) ;
363
+ } ) ) ;
364
+
365
+ it ( 'should select all options using the select all method' , ( ) => {
366
+ let selectedOptions = optionInstances . filter ( option => option . selected ) ;
367
+ testComponent . isMultiselectable = true ;
368
+ fixture . detectChanges ( ) ;
369
+
370
+ expect ( selectedOptions . length ) . toBe ( 0 ) ;
371
+ expect ( optionElements [ 0 ] . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
372
+ expect ( optionInstances [ 0 ] . selected ) . toBeFalse ( ) ;
373
+ expect ( fixture . componentInstance . changedOption ) . toBeUndefined ( ) ;
374
+
375
+ listboxInstance . setAllSelected ( true ) ;
376
+ fixture . detectChanges ( ) ;
377
+
378
+ selectedOptions = optionInstances . filter ( option => option . selected ) ;
379
+ expect ( selectedOptions . length ) . toBe ( 4 ) ;
380
+
381
+ for ( const option of optionElements ) {
382
+ expect ( option . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
383
+ }
384
+
385
+ expect ( fixture . componentInstance . changedOption ) . toBeDefined ( ) ;
386
+ expect ( fixture . componentInstance . changedOption . id ) . toBe ( optionInstances [ 3 ] . id ) ;
387
+ } ) ;
388
+
389
+ it ( 'should deselect previously selected when multiple is false' , ( ) => {
390
+ let selectedOptions = optionInstances . filter ( option => option . selected ) ;
391
+
392
+ expect ( selectedOptions . length ) . toBe ( 0 ) ;
393
+ expect ( optionElements [ 0 ] . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
394
+ expect ( optionInstances [ 0 ] . selected ) . toBeFalse ( ) ;
395
+ expect ( fixture . componentInstance . changedOption ) . toBeUndefined ( ) ;
396
+
397
+ dispatchMouseEvent ( optionElements [ 0 ] , 'click' ) ;
398
+ fixture . detectChanges ( ) ;
399
+
400
+ selectedOptions = optionInstances . filter ( option => option . selected ) ;
401
+ expect ( selectedOptions . length ) . toBe ( 1 ) ;
402
+ expect ( optionElements [ 0 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
403
+ expect ( optionInstances [ 0 ] . selected ) . toBeTrue ( ) ;
404
+ expect ( fixture . componentInstance . changedOption . id ) . toBe ( optionInstances [ 0 ] . id ) ;
405
+
406
+ dispatchMouseEvent ( optionElements [ 2 ] , 'click' ) ;
407
+ fixture . detectChanges ( ) ;
408
+
409
+ selectedOptions = optionInstances . filter ( option => option . selected ) ;
410
+ expect ( selectedOptions . length ) . toBe ( 1 ) ;
411
+ expect ( optionElements [ 0 ] . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
412
+ expect ( optionInstances [ 0 ] . selected ) . toBeFalse ( ) ;
413
+ expect ( optionElements [ 2 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
414
+ expect ( optionInstances [ 2 ] . selected ) . toBeTrue ( ) ;
415
+
416
+ /** Expect first option to be most recently changed because it was deselected. */
417
+ expect ( fixture . componentInstance . changedOption . id ) . toBe ( optionInstances [ 0 ] . id ) ;
418
+ } ) ;
419
+
420
+ it ( 'should allow multiple selection when multiple is true' , ( ) => {
421
+ let selectedOptions = optionInstances . filter ( option => option . selected ) ;
422
+ testComponent . isMultiselectable = true ;
423
+
424
+ expect ( selectedOptions . length ) . toBe ( 0 ) ;
425
+ expect ( fixture . componentInstance . changedOption ) . toBeUndefined ( ) ;
426
+
427
+ dispatchMouseEvent ( optionElements [ 0 ] , 'click' ) ;
428
+ fixture . detectChanges ( ) ;
429
+
430
+ selectedOptions = optionInstances . filter ( option => option . selected ) ;
431
+ expect ( selectedOptions . length ) . toBe ( 1 ) ;
432
+ expect ( optionElements [ 0 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
433
+ expect ( optionInstances [ 0 ] . selected ) . toBeTrue ( ) ;
434
+ expect ( fixture . componentInstance . changedOption . id ) . toBe ( optionInstances [ 0 ] . id ) ;
435
+
436
+ dispatchMouseEvent ( optionElements [ 2 ] , 'click' ) ;
437
+ fixture . detectChanges ( ) ;
438
+
439
+ selectedOptions = optionInstances . filter ( option => option . selected ) ;
440
+ expect ( selectedOptions . length ) . toBe ( 2 ) ;
441
+ expect ( optionElements [ 0 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
442
+ expect ( optionInstances [ 0 ] . selected ) . toBeTrue ( ) ;
443
+ expect ( optionElements [ 2 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
444
+ expect ( optionInstances [ 2 ] . selected ) . toBeTrue ( ) ;
445
+ expect ( fixture . componentInstance . changedOption . id ) . toBe ( optionInstances [ 2 ] . id ) ;
446
+ } ) ;
447
+
448
+ it ( 'should deselect all options when multiple switches to false' , ( ) => {
449
+ let selectedOptions = optionInstances . filter ( option => option . selected ) ;
450
+ testComponent . isMultiselectable = true ;
451
+
452
+ expect ( selectedOptions . length ) . toBe ( 0 ) ;
453
+ expect ( fixture . componentInstance . changedOption ) . toBeUndefined ( ) ;
454
+
455
+ dispatchMouseEvent ( optionElements [ 0 ] , 'click' ) ;
456
+ fixture . detectChanges ( ) ;
457
+
458
+ selectedOptions = optionInstances . filter ( option => option . selected ) ;
459
+ expect ( selectedOptions . length ) . toBe ( 1 ) ;
460
+ expect ( optionElements [ 0 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
461
+ expect ( optionInstances [ 0 ] . selected ) . toBeTrue ( ) ;
462
+ expect ( fixture . componentInstance . changedOption . id ) . toBe ( optionInstances [ 0 ] . id ) ;
463
+
464
+ testComponent . isMultiselectable = false ;
465
+ fixture . detectChanges ( ) ;
466
+
467
+ selectedOptions = optionInstances . filter ( option => option . selected ) ;
468
+ expect ( selectedOptions . length ) . toBe ( 0 ) ;
469
+ expect ( optionElements [ 0 ] . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
470
+ expect ( optionInstances [ 0 ] . selected ) . toBeFalse ( ) ;
471
+ expect ( fixture . componentInstance . changedOption . id ) . toBe ( optionInstances [ 0 ] . id ) ;
472
+ } ) ;
311
473
} ) ;
312
474
475
+ describe ( 'with aria active descendant' , ( ) => {
476
+ let fixture : ComponentFixture < ListboxActiveDescendant > ;
477
+
478
+ let testComponent : ListboxActiveDescendant ;
479
+
480
+ let listbox : DebugElement ;
481
+ let listboxInstance : CdkListbox ;
482
+ let listboxElement : HTMLElement ;
483
+
484
+ let options : DebugElement [ ] ;
485
+ let optionInstances : CdkOption [ ] ;
486
+ let optionElements : HTMLElement [ ] ;
487
+
488
+ beforeEach ( async ( ( ) => {
489
+ TestBed . configureTestingModule ( {
490
+ imports : [ CdkListboxModule ] ,
491
+ declarations : [ ListboxActiveDescendant ] ,
492
+ } ) . compileComponents ( ) ;
493
+ } ) ) ;
494
+
495
+ beforeEach ( async ( ( ) => {
496
+ fixture = TestBed . createComponent ( ListboxActiveDescendant ) ;
497
+ fixture . detectChanges ( ) ;
498
+
499
+ testComponent = fixture . debugElement . componentInstance ;
500
+
501
+ listbox = fixture . debugElement . query ( By . directive ( CdkListbox ) ) ;
502
+ listboxInstance = listbox . injector . get < CdkListbox > ( CdkListbox ) ;
503
+ listboxElement = listbox . nativeElement ;
504
+
505
+ options = fixture . debugElement . queryAll ( By . directive ( CdkOption ) ) ;
506
+ optionInstances = options . map ( o => o . injector . get < CdkOption > ( CdkOption ) ) ;
507
+ optionElements = options . map ( o => o . nativeElement ) ;
508
+ } ) ) ;
509
+
510
+ it ( 'should update aria active descendant when enabled' , ( ) => {
511
+ expect ( listboxElement . hasAttribute ( 'aria-activedescendant' ) ) . toBeFalse ( ) ;
512
+
513
+ listboxInstance . setActiveOption ( optionInstances [ 0 ] ) ;
514
+ fixture . detectChanges ( ) ;
515
+
516
+ expect ( listboxElement . hasAttribute ( 'aria-activedescendant' ) ) . toBeTrue ( ) ;
517
+ expect ( listboxElement . getAttribute ( 'aria-activedescendant' ) ) . toBe ( optionElements [ 0 ] . id ) ;
518
+
519
+ listboxInstance . setActiveOption ( optionInstances [ 2 ] ) ;
520
+ fixture . detectChanges ( ) ;
521
+
522
+ expect ( listboxElement . hasAttribute ( 'aria-activedescendant' ) ) . toBeTrue ( ) ;
523
+ expect ( listboxElement . getAttribute ( 'aria-activedescendant' ) ) . toBe ( optionElements [ 2 ] . id ) ;
524
+ } ) ;
525
+
526
+ it ( 'should update aria active descendant via arrow keys' , ( ) => {
527
+ expect ( listboxElement . hasAttribute ( 'aria-activedescendant' ) ) . toBeFalse ( ) ;
528
+
529
+ dispatchKeyboardEvent ( listboxElement , 'keydown' , DOWN_ARROW ) ;
530
+ fixture . detectChanges ( ) ;
531
+
532
+ expect ( listboxElement . hasAttribute ( 'aria-activedescendant' ) ) . toBeTrue ( ) ;
533
+ expect ( listboxElement . getAttribute ( 'aria-activedescendant' ) ) . toBe ( optionElements [ 0 ] . id ) ;
534
+
535
+ dispatchKeyboardEvent ( listboxElement , 'keydown' , DOWN_ARROW ) ;
536
+ fixture . detectChanges ( ) ;
537
+
538
+ expect ( listboxElement . hasAttribute ( 'aria-activedescendant' ) ) . toBeTrue ( ) ;
539
+ expect ( listboxElement . getAttribute ( 'aria-activedescendant' ) ) . toBe ( optionElements [ 1 ] . id ) ;
540
+ } ) ;
541
+
542
+ it ( 'should place focus on options and not set active descendant' , ( ) => {
543
+ testComponent . isActiveDescendant = false ;
544
+ fixture . detectChanges ( ) ;
545
+
546
+ expect ( listboxElement . hasAttribute ( 'aria-activedescendant' ) ) . toBeFalse ( ) ;
547
+
548
+ dispatchKeyboardEvent ( listboxElement , 'keydown' , DOWN_ARROW ) ;
549
+ fixture . detectChanges ( ) ;
550
+
551
+ expect ( listboxElement . hasAttribute ( 'aria-activedescendant' ) ) . toBeFalse ( ) ;
552
+ expect ( document . activeElement ) . toEqual ( optionElements [ 0 ] ) ;
553
+ dispatchKeyboardEvent ( listboxElement , 'keydown' , DOWN_ARROW ) ;
554
+ fixture . detectChanges ( ) ;
555
+
556
+ expect ( listboxElement . hasAttribute ( 'aria-activedescendant' ) ) . toBeFalse ( ) ;
557
+ expect ( document . activeElement ) . toEqual ( optionElements [ 1 ] ) ;
558
+
559
+ } ) ;
560
+ } ) ;
313
561
} ) ;
314
562
315
563
@Component ( {
316
564
template : `
317
565
<div cdkListbox
318
- [disabled]="isListboxDisabled"
319
- (selectionChange)="onSelectionChange($event)">
566
+ [disabled]="isListboxDisabled"
567
+ (selectionChange)="onSelectionChange($event)">
320
568
<div cdkOption
321
569
[disabled]="isPurpleDisabled">
322
- Purple</div>
570
+ Purple
571
+ </div>
323
572
<div cdkOption
324
573
[disabled]="isSolarDisabled">
325
- Solar</div>
574
+ Solar
575
+ </div>
326
576
<div cdkOption>Arc</div>
327
577
<div cdkOption>Stasis</div>
328
578
</div>`
@@ -337,3 +587,47 @@ class ListboxWithOptions {
337
587
this . changedOption = event . option ;
338
588
}
339
589
}
590
+
591
+ @Component ( {
592
+ template : `
593
+ <div cdkListbox
594
+ [multiple]="isMultiselectable"
595
+ (selectionChange)="onSelectionChange($event)">
596
+ <div cdkOption>Purple</div>
597
+ <div cdkOption>Solar</div>
598
+ <div cdkOption>Arc</div>
599
+ <div cdkOption>Stasis</div>
600
+ </div>`
601
+ } )
602
+ class ListboxMultiselect {
603
+ changedOption : CdkOption ;
604
+ isMultiselectable : boolean = false ;
605
+
606
+ onSelectionChange ( event : ListboxSelectionChangeEvent ) {
607
+ this . changedOption = event . option ;
608
+ }
609
+ }
610
+
611
+ @Component ( {
612
+ template : `
613
+ <div cdkListbox
614
+ [useActiveDescendant]="isActiveDescendant">
615
+ <div cdkOption>Purple</div>
616
+ <div cdkOption>Solar</div>
617
+ <div cdkOption>Arc</div>
618
+ <div cdkOption>Stasis</div>
619
+ </div>`
620
+ } )
621
+ class ListboxActiveDescendant {
622
+ changedOption : CdkOption ;
623
+ isActiveDescendant : boolean = true ;
624
+ focusedOption : string ;
625
+
626
+ onSelectionChange ( event : ListboxSelectionChangeEvent ) {
627
+ this . changedOption = event . option ;
628
+ }
629
+
630
+ onFocus ( option : string ) {
631
+ this . focusedOption = option ;
632
+ }
633
+ }
0 commit comments