@@ -3,7 +3,17 @@ import {Component, Type} from '@angular/core';
3
3
import { By } from '@angular/platform-browser' ;
4
4
import { CdkListbox , CdkListboxModule , CdkOption , ListboxValueChangeEvent } from './index' ;
5
5
import { dispatchKeyboardEvent , dispatchMouseEvent } from '../../cdk/testing/private' ;
6
- import { B , DOWN_ARROW , END , HOME , SPACE , UP_ARROW } from '@angular/cdk/keycodes' ;
6
+ import {
7
+ A ,
8
+ B ,
9
+ DOWN_ARROW ,
10
+ END ,
11
+ HOME ,
12
+ LEFT_ARROW ,
13
+ RIGHT_ARROW ,
14
+ SPACE ,
15
+ UP_ARROW ,
16
+ } from '@angular/cdk/keycodes' ;
7
17
import { FormControl , ReactiveFormsModule } from '@angular/forms' ;
8
18
import { CommonModule } from '@angular/common' ;
9
19
@@ -132,6 +142,27 @@ describe('CdkOption and CdkListbox', () => {
132
142
expect ( fixture . componentInstance . changedOption ?. id ) . toBe ( options [ 0 ] . id ) ;
133
143
} ) ;
134
144
145
+ it ( 'should select and deselect range on option SHIFT + click' , async ( ) => {
146
+ const { testComponent, fixture, listbox, optionEls} = await setupComponent ( ListboxWithOptions ) ;
147
+ testComponent . isMultiselectable = true ;
148
+ fixture . detectChanges ( ) ;
149
+
150
+ dispatchMouseEvent ( optionEls [ 1 ] , 'click' , undefined , undefined , undefined , { shift : true } ) ;
151
+ fixture . detectChanges ( ) ;
152
+
153
+ expect ( listbox . value ) . toEqual ( [ 'orange' ] ) ;
154
+
155
+ dispatchMouseEvent ( optionEls [ 3 ] , 'click' , undefined , undefined , undefined , { shift : true } ) ;
156
+ fixture . detectChanges ( ) ;
157
+
158
+ expect ( listbox . value ) . toEqual ( [ 'orange' , 'banana' , 'peach' ] ) ;
159
+
160
+ dispatchMouseEvent ( optionEls [ 2 ] , 'click' , undefined , undefined , undefined , { shift : true } ) ;
161
+ fixture . detectChanges ( ) ;
162
+
163
+ expect ( listbox . value ) . toEqual ( [ 'orange' ] ) ;
164
+ } ) ;
165
+
135
166
it ( 'should update on option activated via keyboard' , async ( ) => {
136
167
const { fixture, listbox, listboxEl, options, optionEls} = await setupComponent (
137
168
ListboxWithOptions ,
@@ -260,20 +291,19 @@ describe('CdkOption and CdkListbox', () => {
260
291
expect ( options [ 1 ] . isSelected ( ) ) . toBeFalse ( ) ;
261
292
} ) ;
262
293
263
- // TODO(mmalerba): Fix this case.
264
- // Currently banana gets booted because the option isn't loaded yet,
265
- // but then when the option loads the value is already lost.
266
- // it('should allow binding to listbox value', async () => {
267
- // const {testComponent, fixture, listbox, options} = await setupComponent(ListboxWithBoundValue);
268
- // expect(listbox.value).toEqual(['banana']);
269
- // expect(options[2].isSelected()).toBeTrue();
270
- //
271
- // testComponent.value = ['orange'];
272
- // fixture.detectChanges();
273
- //
274
- // expect(listbox.value).toEqual(['orange']);
275
- // expect(options[1].isSelected()).toBeTrue();
276
- // });
294
+ it ( 'should allow binding to listbox value' , async ( ) => {
295
+ const { testComponent, fixture, listbox, options} = await setupComponent (
296
+ ListboxWithBoundValue ,
297
+ ) ;
298
+ expect ( listbox . value ) . toEqual ( [ 'banana' ] ) ;
299
+ expect ( options [ 2 ] . isSelected ( ) ) . toBeTrue ( ) ;
300
+
301
+ testComponent . value = [ 'orange' ] ;
302
+ fixture . detectChanges ( ) ;
303
+
304
+ expect ( listbox . value ) . toEqual ( [ 'orange' ] ) ;
305
+ expect ( options [ 1 ] . isSelected ( ) ) . toBeTrue ( ) ;
306
+ } ) ;
277
307
} ) ;
278
308
279
309
describe ( 'disabled state' , ( ) => {
@@ -482,7 +512,105 @@ describe('CdkOption and CdkListbox', () => {
482
512
expect ( fixture . componentInstance . changedOption ?. id ) . toBe ( options [ 1 ] . id ) ;
483
513
} ) ;
484
514
485
- // TODO(mmalerba): ensure all keys covered
515
+ it ( 'should update active item on arrow key presses in horizontal mode' , async ( ) => {
516
+ const { testComponent, fixture, listbox, listboxEl, options} = await setupComponent (
517
+ ListboxWithOptions ,
518
+ ) ;
519
+ testComponent . orientation = 'horizontal' ;
520
+ fixture . detectChanges ( ) ;
521
+
522
+ expect ( listboxEl . getAttribute ( 'aria-orientation' ) ) . toBe ( 'horizontal' ) ;
523
+
524
+ listbox . focus ( ) ;
525
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , RIGHT_ARROW ) ;
526
+ fixture . detectChanges ( ) ;
527
+
528
+ expect ( options [ 1 ] . isActive ( ) ) . toBeTrue ( ) ;
529
+
530
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , LEFT_ARROW ) ;
531
+ fixture . detectChanges ( ) ;
532
+
533
+ expect ( options [ 0 ] . isActive ( ) ) . toBeTrue ( ) ;
534
+ } ) ;
535
+
536
+ it ( 'should select and deselect all option with CONTROL + A' , async ( ) => {
537
+ const { testComponent, fixture, listbox, listboxEl} = await setupComponent ( ListboxWithOptions ) ;
538
+ testComponent . isMultiselectable = true ;
539
+ fixture . detectChanges ( ) ;
540
+
541
+ listbox . focus ( ) ;
542
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , A , undefined , { control : true } ) ;
543
+ fixture . detectChanges ( ) ;
544
+
545
+ expect ( listbox . value ) . toEqual ( [ 'apple' , 'orange' , 'banana' , 'peach' ] ) ;
546
+
547
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , A , undefined , { control : true } ) ;
548
+ fixture . detectChanges ( ) ;
549
+
550
+ expect ( listbox . value ) . toEqual ( [ ] ) ;
551
+ } ) ;
552
+
553
+ it ( 'should select and deselect range with CONTROL + SPACE' , async ( ) => {
554
+ const { testComponent, fixture, listbox, listboxEl} = await setupComponent ( ListboxWithOptions ) ;
555
+ testComponent . isMultiselectable = true ;
556
+ fixture . detectChanges ( ) ;
557
+
558
+ listbox . focus ( ) ;
559
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , DOWN_ARROW ) ;
560
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , SPACE , undefined , { shift : true } ) ;
561
+ fixture . detectChanges ( ) ;
562
+
563
+ expect ( listbox . value ) . toEqual ( [ 'orange' ] ) ;
564
+
565
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , DOWN_ARROW ) ;
566
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , DOWN_ARROW ) ;
567
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , SPACE , undefined , { shift : true } ) ;
568
+ fixture . detectChanges ( ) ;
569
+
570
+ expect ( listbox . value ) . toEqual ( [ 'orange' , 'banana' , 'peach' ] ) ;
571
+
572
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , UP_ARROW ) ;
573
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , SPACE , undefined , { shift : true } ) ;
574
+
575
+ expect ( listbox . value ) . toEqual ( [ 'orange' ] ) ;
576
+ } ) ;
577
+
578
+ it ( 'should select and deselect range with CONTROL + SHIFT + HOME' , async ( ) => {
579
+ const { testComponent, fixture, listbox, listboxEl} = await setupComponent ( ListboxWithOptions ) ;
580
+ testComponent . isMultiselectable = true ;
581
+ listbox . focus ( ) ;
582
+ fixture . detectChanges ( ) ;
583
+
584
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , DOWN_ARROW ) ;
585
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , DOWN_ARROW ) ;
586
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , HOME , undefined , { control : true , shift : true } ) ;
587
+
588
+ expect ( listbox . value ) . toEqual ( [ 'apple' , 'orange' , 'banana' ] ) ;
589
+
590
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , DOWN_ARROW ) ;
591
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , DOWN_ARROW ) ;
592
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , HOME , undefined , { control : true , shift : true } ) ;
593
+
594
+ expect ( listbox . value ) . toEqual ( [ ] ) ;
595
+ } ) ;
596
+
597
+ it ( 'should select and deselect range with CONTROL + SHIFT + END' , async ( ) => {
598
+ const { testComponent, fixture, listbox, listboxEl} = await setupComponent ( ListboxWithOptions ) ;
599
+ testComponent . isMultiselectable = true ;
600
+ listbox . focus ( ) ;
601
+ fixture . detectChanges ( ) ;
602
+
603
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , DOWN_ARROW ) ;
604
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , END , undefined , { control : true , shift : true } ) ;
605
+
606
+ expect ( listbox . value ) . toEqual ( [ 'orange' , 'banana' , 'peach' ] ) ;
607
+
608
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , UP_ARROW ) ;
609
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , UP_ARROW ) ;
610
+ dispatchKeyboardEvent ( listboxEl , 'keydown' , END , undefined , { control : true , shift : true } ) ;
611
+
612
+ expect ( listbox . value ) . toEqual ( [ ] ) ;
613
+ } ) ;
486
614
} ) ;
487
615
488
616
describe ( 'with roving tabindex' , ( ) => {
@@ -639,15 +767,15 @@ describe('CdkOption and CdkListbox', () => {
639
767
subscription . unsubscribe ( ) ;
640
768
} ) ;
641
769
642
- it ( 'should have FormControl error multiple values selected in single-select listbox' , async ( ) => {
770
+ it ( 'should have FormControl error when multiple values selected in single-select listbox' , async ( ) => {
643
771
const { testComponent, fixture} = await setupComponent ( ListboxWithFormControl , [
644
772
ReactiveFormsModule ,
645
773
] ) ;
646
774
testComponent . formControl . setValue ( [ 'orange' , 'banana' ] ) ;
647
775
fixture . detectChanges ( ) ;
648
776
649
- expect ( testComponent . formControl . hasError ( 'cdkListboxMultipleValues ' ) ) . toBeTrue ( ) ;
650
- expect ( testComponent . formControl . hasError ( 'cdkListboxInvalidValues ' ) ) . toBeFalse ( ) ;
777
+ expect ( testComponent . formControl . hasError ( 'cdkListboxUnexpectedMultipleValues ' ) ) . toBeTrue ( ) ;
778
+ expect ( testComponent . formControl . hasError ( 'cdkListboxUnexpectedOptionValues ' ) ) . toBeFalse ( ) ;
651
779
} ) ;
652
780
653
781
it ( 'should have FormControl error when non-option value selected' , async ( ) => {
@@ -658,9 +786,9 @@ describe('CdkOption and CdkListbox', () => {
658
786
testComponent . formControl . setValue ( [ 'orange' , 'dragonfruit' , 'mango' ] ) ;
659
787
fixture . detectChanges ( ) ;
660
788
661
- expect ( testComponent . formControl . hasError ( 'cdkListboxInvalidValues ' ) ) . toBeTrue ( ) ;
662
- expect ( testComponent . formControl . hasError ( 'cdkListboxMultipleValues ' ) ) . toBeFalse ( ) ;
663
- expect ( testComponent . formControl . errors ?. [ 'cdkListboxInvalidValues ' ] ) . toEqual ( {
789
+ expect ( testComponent . formControl . hasError ( 'cdkListboxUnexpectedOptionValues ' ) ) . toBeTrue ( ) ;
790
+ expect ( testComponent . formControl . hasError ( 'cdkListboxUnexpectedMultipleValues ' ) ) . toBeFalse ( ) ;
791
+ expect ( testComponent . formControl . errors ?. [ 'cdkListboxUnexpectedOptionValues ' ] ) . toEqual ( {
664
792
'values' : [ 'dragonfruit' , 'mango' ] ,
665
793
} ) ;
666
794
} ) ;
@@ -672,9 +800,9 @@ describe('CdkOption and CdkListbox', () => {
672
800
testComponent . formControl . setValue ( [ 'dragonfruit' , 'mango' ] ) ;
673
801
fixture . detectChanges ( ) ;
674
802
675
- expect ( testComponent . formControl . hasError ( 'cdkListboxInvalidValues ' ) ) . toBeTrue ( ) ;
676
- expect ( testComponent . formControl . hasError ( 'cdkListboxMultipleValues ' ) ) . toBeTrue ( ) ;
677
- expect ( testComponent . formControl . errors ?. [ 'cdkListboxInvalidValues ' ] ) . toEqual ( {
803
+ expect ( testComponent . formControl . hasError ( 'cdkListboxUnexpectedOptionValues ' ) ) . toBeTrue ( ) ;
804
+ expect ( testComponent . formControl . hasError ( 'cdkListboxUnexpectedMultipleValues ' ) ) . toBeTrue ( ) ;
805
+ expect ( testComponent . formControl . errors ?. [ 'cdkListboxUnexpectedOptionValues ' ] ) . toEqual ( {
678
806
'values' : [ 'dragonfruit' , 'mango' ] ,
679
807
} ) ;
680
808
} ) ;
@@ -689,6 +817,7 @@ describe('CdkOption and CdkListbox', () => {
689
817
[cdkListboxMultiple]="isMultiselectable"
690
818
[cdkListboxDisabled]="isListboxDisabled"
691
819
[cdkListboxUseActiveDescendant]="isActiveDescendant"
820
+ [cdkListboxOrientation]="orientation"
692
821
(cdkListboxValueChange)="onSelectionChange($event)">
693
822
<div cdkOption="apple"
694
823
[cdkOptionDisabled]="isAppleDisabled"
@@ -703,7 +832,7 @@ describe('CdkOption and CdkListbox', () => {
703
832
` ,
704
833
} )
705
834
class ListboxWithOptions {
706
- changedOption : CdkOption ;
835
+ changedOption : CdkOption | null ;
707
836
isListboxDisabled = false ;
708
837
isAppleDisabled = false ;
709
838
isOrangeDisabled = false ;
@@ -713,6 +842,7 @@ class ListboxWithOptions {
713
842
listboxTabindex : number ;
714
843
appleId : string ;
715
844
appleTabindex : number ;
845
+ orientation : 'horizontal' | 'vertical' = 'vertical' ;
716
846
717
847
onSelectionChange ( event : ListboxValueChangeEvent < unknown > ) {
718
848
this . changedOption = event . option ;
@@ -755,20 +885,20 @@ class ListboxWithFormControl {
755
885
} )
756
886
class ListboxWithCustomTypeahead { }
757
887
758
- // @Component ({
759
- // template: `
760
- // <div cdkListbox
761
- // [cdkListboxValue]="value">
762
- // <div cdkOption="apple">Apple</div>
763
- // <div cdkOption="orange">Orange</div>
764
- // <div cdkOption="banana">Banana</div>
765
- // <div cdkOption="peach">Peach</div>
766
- // </div>
767
- // `,
768
- // })
769
- // class ListboxWithBoundValue {
770
- // value = ['banana'];
771
- // }
888
+ @Component ( {
889
+ template : `
890
+ <div cdkListbox
891
+ [cdkListboxValue]="value">
892
+ <div cdkOption="apple">Apple</div>
893
+ <div cdkOption="orange">Orange</div>
894
+ <div cdkOption="banana">Banana</div>
895
+ <div cdkOption="peach">Peach</div>
896
+ </div>
897
+ ` ,
898
+ } )
899
+ class ListboxWithBoundValue {
900
+ value = [ 'banana' ] ;
901
+ }
772
902
773
903
@Component ( {
774
904
template : `
0 commit comments