3
3
async ,
4
4
TestBed , tick , fakeAsync ,
5
5
} from '@angular/core/testing' ;
6
- import { Component , DebugElement } from '@angular/core' ;
6
+ import { Component , DebugElement , ViewChild } from '@angular/core' ;
7
7
import { By } from '@angular/platform-browser' ;
8
8
import {
9
9
CdkOption ,
@@ -15,16 +15,17 @@ import {
15
15
dispatchMouseEvent
16
16
} from '@angular/cdk/testing/private' ;
17
17
import { A , DOWN_ARROW , END , HOME , SPACE } from '@angular/cdk/keycodes' ;
18
+ import { FormControl , FormsModule , ReactiveFormsModule } from '@angular/forms' ;
18
19
19
- describe ( 'CdkOption' , ( ) => {
20
+ describe ( 'CdkOption and CdkListbox ' , ( ) => {
20
21
21
22
describe ( 'selection state change' , ( ) => {
22
23
let fixture : ComponentFixture < ListboxWithOptions > ;
23
24
24
25
let testComponent : ListboxWithOptions ;
25
26
26
27
let listbox : DebugElement ;
27
- let listboxInstance : CdkListbox ;
28
+ let listboxInstance : CdkListbox < unknown > ;
28
29
let listboxElement : HTMLElement ;
29
30
30
31
let options : DebugElement [ ] ;
@@ -45,7 +46,7 @@ describe('CdkOption', () => {
45
46
testComponent = fixture . debugElement . componentInstance ;
46
47
47
48
listbox = fixture . debugElement . query ( By . directive ( CdkListbox ) ) ;
48
- listboxInstance = listbox . injector . get < CdkListbox > ( CdkListbox ) ;
49
+ listboxInstance = listbox . injector . get < CdkListbox < unknown > > ( CdkListbox ) ;
49
50
listboxElement = listbox . nativeElement ;
50
51
51
52
options = fixture . debugElement . queryAll ( By . directive ( CdkOption ) ) ;
@@ -360,7 +361,7 @@ describe('CdkOption', () => {
360
361
361
362
let testComponent : ListboxMultiselect ;
362
363
let listbox : DebugElement ;
363
- let listboxInstance : CdkListbox ;
364
+ let listboxInstance : CdkListbox < unknown > ;
364
365
365
366
let options : DebugElement [ ] ;
366
367
let optionInstances : CdkOption [ ] ;
@@ -379,7 +380,7 @@ describe('CdkOption', () => {
379
380
380
381
testComponent = fixture . debugElement . componentInstance ;
381
382
listbox = fixture . debugElement . query ( By . directive ( CdkListbox ) ) ;
382
- listboxInstance = listbox . injector . get < CdkListbox > ( CdkListbox ) ;
383
+ listboxInstance = listbox . injector . get < CdkListbox < unknown > > ( CdkListbox ) ;
383
384
384
385
options = fixture . debugElement . queryAll ( By . directive ( CdkOption ) ) ;
385
386
optionInstances = options . map ( o => o . injector . get < CdkOption > ( CdkOption ) ) ;
@@ -502,7 +503,7 @@ describe('CdkOption', () => {
502
503
let testComponent : ListboxActiveDescendant ;
503
504
504
505
let listbox : DebugElement ;
505
- let listboxInstance : CdkListbox ;
506
+ let listboxInstance : CdkListbox < unknown > ;
506
507
let listboxElement : HTMLElement ;
507
508
508
509
let options : DebugElement [ ] ;
@@ -523,7 +524,7 @@ describe('CdkOption', () => {
523
524
testComponent = fixture . debugElement . componentInstance ;
524
525
525
526
listbox = fixture . debugElement . query ( By . directive ( CdkListbox ) ) ;
526
- listboxInstance = listbox . injector . get < CdkListbox > ( CdkListbox ) ;
527
+ listboxInstance = listbox . injector . get < CdkListbox < unknown > > ( CdkListbox ) ;
527
528
listboxElement = listbox . nativeElement ;
528
529
529
530
options = fixture . debugElement . queryAll ( By . directive ( CdkOption ) ) ;
@@ -582,6 +583,185 @@ describe('CdkOption', () => {
582
583
583
584
} ) ;
584
585
} ) ;
586
+
587
+ describe ( 'with control value accessor implemented' , ( ) => {
588
+ let fixture : ComponentFixture < ListboxControlValueAccessor > ;
589
+ let testComponent : ListboxControlValueAccessor ;
590
+
591
+ let listbox : DebugElement ;
592
+ let listboxInstance : CdkListbox < string > ;
593
+
594
+ let options : DebugElement [ ] ;
595
+ let optionInstances : CdkOption [ ] ;
596
+ let optionElements : HTMLElement [ ] ;
597
+
598
+ beforeEach ( async ( ( ) => {
599
+ TestBed . configureTestingModule ( {
600
+ imports : [ CdkListboxModule , FormsModule , ReactiveFormsModule ] ,
601
+ declarations : [ ListboxControlValueAccessor ] ,
602
+ } ) . compileComponents ( ) ;
603
+ } ) ) ;
604
+
605
+ beforeEach ( ( ) => {
606
+ fixture = TestBed . createComponent ( ListboxControlValueAccessor ) ;
607
+ fixture . detectChanges ( ) ;
608
+
609
+ testComponent = fixture . debugElement . componentInstance ;
610
+
611
+ listbox = fixture . debugElement . query ( By . directive ( CdkListbox ) ) ;
612
+ listboxInstance = listbox . injector . get < CdkListbox < string > > ( CdkListbox ) ;
613
+
614
+ options = fixture . debugElement . queryAll ( By . directive ( CdkOption ) ) ;
615
+ optionInstances = options . map ( o => o . injector . get < CdkOption > ( CdkOption ) ) ;
616
+ optionElements = options . map ( o => o . nativeElement ) ;
617
+ } ) ;
618
+
619
+ it ( 'should be able to set the disabled state via setDisabledState' , ( ) => {
620
+ expect ( listboxInstance . disabled )
621
+ . toBe ( false , 'Expected the selection list to be enabled.' ) ;
622
+ expect ( optionInstances . every ( option => ! option . disabled ) )
623
+ . toBe ( true , 'Expected every list option to be enabled.' ) ;
624
+
625
+ listboxInstance . setDisabledState ( true ) ;
626
+ fixture . detectChanges ( ) ;
627
+
628
+ expect ( listboxInstance . disabled )
629
+ . toBe ( true , 'Expected the selection list to be disabled.' ) ;
630
+ for ( const option of optionElements ) {
631
+ expect ( option . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) ;
632
+ }
633
+ } ) ;
634
+
635
+ it ( 'should be able to select options via writeValue' , ( ) => {
636
+ expect ( optionInstances . every ( option => ! option . disabled ) )
637
+ . toBe ( true , 'Expected every list option to be enabled.' ) ;
638
+
639
+ listboxInstance . writeValue ( 'arc' ) ;
640
+ fixture . detectChanges ( ) ;
641
+
642
+ expect ( optionElements [ 0 ] . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
643
+ expect ( optionElements [ 1 ] . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
644
+ expect ( optionElements [ 3 ] . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
645
+
646
+ expect ( optionInstances [ 2 ] . selected ) . toBeTrue ( ) ;
647
+ expect ( optionElements [ 2 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
648
+ } ) ;
649
+
650
+ it ( 'should be select multiple options by their values' , ( ) => {
651
+ expect ( optionInstances . every ( option => ! option . disabled ) )
652
+ . toBe ( true , 'Expected every list option to be enabled.' ) ;
653
+
654
+ testComponent . isMultiselectable = true ;
655
+ fixture . detectChanges ( ) ;
656
+
657
+ listboxInstance . writeValue ( [ 'arc' , 'stasis' ] ) ;
658
+ fixture . detectChanges ( ) ;
659
+
660
+ expect ( optionElements [ 0 ] . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
661
+ expect ( optionElements [ 1 ] . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
662
+
663
+ expect ( optionInstances [ 2 ] . selected ) . toBeTrue ( ) ;
664
+ expect ( optionElements [ 2 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
665
+ expect ( optionInstances [ 3 ] . selected ) . toBeTrue ( ) ;
666
+ expect ( optionElements [ 3 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
667
+ } ) ;
668
+
669
+ it ( 'should be able to disable options from the control' , ( ) => {
670
+ expect ( testComponent . listbox . disabled ) . toBeFalse ( ) ;
671
+ expect ( optionInstances . every ( option => ! option . disabled ) )
672
+ . toBe ( true , 'Expected every list option to be enabled.' ) ;
673
+
674
+ testComponent . form . disable ( ) ;
675
+ fixture . detectChanges ( ) ;
676
+
677
+ expect ( testComponent . listbox . disabled ) . toBeTrue ( ) ;
678
+ for ( const option of optionElements ) {
679
+ expect ( option . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) ;
680
+ }
681
+ } ) ;
682
+
683
+ it ( 'should be able to toggle disabled state after form control is disabled' , ( ) => {
684
+ expect ( testComponent . listbox . disabled ) . toBeFalse ( ) ;
685
+ expect ( optionInstances . every ( option => ! option . disabled ) )
686
+ . toBe ( true , 'Expected every list option to be enabled.' ) ;
687
+
688
+ testComponent . form . disable ( ) ;
689
+ fixture . detectChanges ( ) ;
690
+
691
+ expect ( testComponent . listbox . disabled ) . toBeTrue ( ) ;
692
+ for ( const option of optionElements ) {
693
+ expect ( option . getAttribute ( 'aria-disabled' ) ) . toBe ( 'true' ) ;
694
+ }
695
+
696
+ listboxInstance . disabled = false ;
697
+ fixture . detectChanges ( ) ;
698
+
699
+ expect ( testComponent . listbox . disabled ) . toBeFalse ( ) ;
700
+ expect ( optionInstances . every ( option => ! option . disabled ) )
701
+ . toBe ( true , 'Expected every list option to be enabled.' ) ;
702
+ } ) ;
703
+
704
+ it ( 'should be able to select options via setting the value in form control' , ( ) => {
705
+ expect ( optionInstances . every ( option => option . selected ) ) . toBeFalse ( ) ;
706
+
707
+ testComponent . isMultiselectable = true ;
708
+ fixture . detectChanges ( ) ;
709
+
710
+ testComponent . form . setValue ( [ 'purple' , 'arc' ] ) ;
711
+ fixture . detectChanges ( ) ;
712
+
713
+ expect ( optionElements [ 0 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
714
+ expect ( optionElements [ 2 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
715
+ expect ( optionInstances [ 0 ] . selected ) . toBeTrue ( ) ;
716
+ expect ( optionInstances [ 2 ] . selected ) . toBeTrue ( ) ;
717
+
718
+ testComponent . form . setValue ( null ) ;
719
+ fixture . detectChanges ( ) ;
720
+
721
+ expect ( optionInstances . every ( option => option . selected ) ) . toBeFalse ( ) ;
722
+ } ) ;
723
+
724
+ it ( 'should only select the first matching option if multiple is not enabled' , ( ) => {
725
+ expect ( optionInstances . every ( option => option . selected ) ) . toBeFalse ( ) ;
726
+
727
+ testComponent . form . setValue ( [ 'solar' , 'arc' ] ) ;
728
+ fixture . detectChanges ( ) ;
729
+
730
+ expect ( optionElements [ 1 ] . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
731
+ expect ( optionElements [ 2 ] . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
732
+ expect ( optionInstances [ 1 ] . selected ) . toBeTrue ( ) ;
733
+ expect ( optionInstances [ 2 ] . selected ) . toBeFalse ( ) ;
734
+ } ) ;
735
+
736
+ it ( 'should deselect an option selected via form control once its value changes' , ( ) => {
737
+ const option = optionInstances [ 1 ] ;
738
+ const element = optionElements [ 1 ] ;
739
+
740
+ testComponent . form . setValue ( [ 'solar' ] ) ;
741
+ fixture . detectChanges ( ) ;
742
+
743
+ expect ( element . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
744
+ expect ( option . selected ) . toBeTrue ( ) ;
745
+
746
+ option . value = 'new-value' ;
747
+ fixture . detectChanges ( ) ;
748
+
749
+ expect ( element . hasAttribute ( 'aria-selected' ) ) . toBeFalse ( ) ;
750
+ expect ( option . selected ) . toBeFalse ( ) ;
751
+ } ) ;
752
+
753
+ it ( 'should maintain the form control on listbox destruction' , function ( ) {
754
+ testComponent . form . setValue ( [ 'solar' ] ) ;
755
+ fixture . detectChanges ( ) ;
756
+
757
+ expect ( testComponent . form . value ) . toEqual ( [ 'solar' ] ) ;
758
+
759
+ testComponent . showListbox = false ;
760
+ fixture . detectChanges ( ) ;
761
+
762
+ expect ( testComponent . form . value ) . toEqual ( [ 'solar' ] ) ;
763
+ } ) ;
764
+ } ) ;
585
765
} ) ;
586
766
587
767
@Component ( {
@@ -607,7 +787,7 @@ class ListboxWithOptions {
607
787
isPurpleDisabled : boolean = false ;
608
788
isSolarDisabled : boolean = false ;
609
789
610
- onSelectionChange ( event : ListboxSelectionChangeEvent ) {
790
+ onSelectionChange ( event : ListboxSelectionChangeEvent < unknown > ) {
611
791
this . changedOption = event . option ;
612
792
}
613
793
}
@@ -627,7 +807,7 @@ class ListboxMultiselect {
627
807
changedOption : CdkOption ;
628
808
isMultiselectable : boolean = false ;
629
809
630
- onSelectionChange ( event : ListboxSelectionChangeEvent ) {
810
+ onSelectionChange ( event : ListboxSelectionChangeEvent < unknown > ) {
631
811
this . changedOption = event . option ;
632
812
}
633
813
}
@@ -647,11 +827,38 @@ class ListboxActiveDescendant {
647
827
isActiveDescendant : boolean = true ;
648
828
focusedOption : string ;
649
829
650
- onSelectionChange ( event : ListboxSelectionChangeEvent ) {
830
+ onSelectionChange ( event : ListboxSelectionChangeEvent < unknown > ) {
651
831
this . changedOption = event . option ;
652
832
}
653
833
654
834
onFocus ( option : string ) {
655
835
this . focusedOption = option ;
656
836
}
657
837
}
838
+
839
+ @Component ( {
840
+ template : `
841
+ <select cdkListbox
842
+ [disabled]="isDisabled"
843
+ [multiple]="isMultiselectable"
844
+ (selectionChange)="onSelectionChange($event)"
845
+ [formControl]="form"
846
+ *ngIf="showListbox" ngDefaultControl>
847
+ <option cdkOption [value]="'purple'">Purple</option>
848
+ <option cdkOption [value]="'solar'">Solar</option>
849
+ <option cdkOption [value]="'arc'">Arc</option>
850
+ <option cdkOption [value]="'stasis'">Stasis</option>
851
+ </select>`
852
+ } )
853
+ class ListboxControlValueAccessor {
854
+ form = new FormControl ( ) ;
855
+ changedOption : CdkOption < string > ;
856
+ isDisabled : boolean = false ;
857
+ isMultiselectable : boolean = false ;
858
+ showListbox : boolean = true ;
859
+ @ViewChild ( CdkListbox ) listbox : CdkListbox < string > ;
860
+
861
+ onSelectionChange ( event : ListboxSelectionChangeEvent < string > ) {
862
+ this . changedOption = event . option ;
863
+ }
864
+ }
0 commit comments