@@ -15,6 +15,7 @@ import {QueryList} from '@angular/core';
15
15
import { take } from 'rxjs/operators' ;
16
16
import { TreeKeyManager , TreeKeyManagerItem } from './tree-key-manager' ;
17
17
import { Observable , of as observableOf , Subscription } from 'rxjs' ;
18
+ import { fakeAsync , tick } from '@angular/core/testing' ;
18
19
19
20
class FakeBaseTreeKeyManagerItem {
20
21
_isExpanded = false ;
@@ -115,14 +116,19 @@ describe('TreeKeyManager', () => {
115
116
FakeArrayTreeKeyManagerItem | FakeObservableTreeKeyManagerItem
116
117
> ;
117
118
119
+ let parentItem : FakeArrayTreeKeyManagerItem | FakeObservableTreeKeyManagerItem ; // index 0
120
+ let childItem : FakeArrayTreeKeyManagerItem | FakeObservableTreeKeyManagerItem ; // index 1
121
+ let childItemWithNoChildren : FakeArrayTreeKeyManagerItem | FakeObservableTreeKeyManagerItem ; // index 3
122
+ let lastItem : FakeArrayTreeKeyManagerItem | FakeObservableTreeKeyManagerItem ; // index 5
123
+
118
124
beforeEach ( ( ) => {
119
125
itemList = new QueryList < FakeArrayTreeKeyManagerItem | FakeObservableTreeKeyManagerItem > ( ) ;
120
- const parent1 = new itemParam . constructor ( 'parent1 ' ) ;
121
- const parent1Child1 = new itemParam . constructor ( 'parent1Child1 ' ) ;
122
- const parent1Child1Child1 = new itemParam . constructor ( 'parent1Child1Child1 ' ) ;
123
- const parent1Child2 = new itemParam . constructor ( 'parent1Child2 ' ) ;
124
- const parent2 = new itemParam . constructor ( 'parent2 ' ) ;
125
- const parent2Child1 = new itemParam . constructor ( 'parent2Child1 ' ) ;
126
+ const parent1 = new itemParam . constructor ( 'one ' ) ;
127
+ const parent1Child1 = new itemParam . constructor ( 'two ' ) ;
128
+ const parent1Child1Child1 = new itemParam . constructor ( 'three ' ) ;
129
+ const parent1Child2 = new itemParam . constructor ( 'four ' ) ;
130
+ const parent2 = new itemParam . constructor ( 'five ' ) ;
131
+ const parent2Child1 = new itemParam . constructor ( 'six ' ) ;
126
132
127
133
parent1 . _children = [ parent1Child1 , parent1Child2 ] ;
128
134
parent1Child1 . _parent = parent1 ;
@@ -132,6 +138,11 @@ describe('TreeKeyManager', () => {
132
138
parent2 . _children = [ parent2Child1 ] ;
133
139
parent2Child1 . _parent = parent2 ;
134
140
141
+ parentItem = parent1 ;
142
+ childItem = parent1Child1 ;
143
+ childItemWithNoChildren = parent1Child2 ;
144
+ lastItem = parent2Child1 ;
145
+
135
146
itemList . reset ( [
136
147
parent1 ,
137
148
parent1Child1 ,
@@ -155,16 +166,12 @@ describe('TreeKeyManager', () => {
155
166
keyManager . onClick ( itemList . get ( 0 ) ! ) ;
156
167
157
168
expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 0 ) ;
158
- expect ( keyManager . getActiveItem ( ) ?. getLabel ( ) )
159
- . withContext ( 'active item label' )
160
- . toBe ( 'parent1' ) ;
169
+ expect ( keyManager . getActiveItem ( ) ?. getLabel ( ) ) . withContext ( 'active item label' ) . toBe ( 'one' ) ;
161
170
itemList . reset ( [ new FakeObservableTreeKeyManagerItem ( 'parent0' ) , ...itemList . toArray ( ) ] ) ;
162
171
itemList . notifyOnChanges ( ) ;
163
172
164
173
expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 1 ) ;
165
- expect ( keyManager . getActiveItem ( ) ?. getLabel ( ) )
166
- . withContext ( 'active item label' )
167
- . toBe ( 'parent1' ) ;
174
+ expect ( keyManager . getActiveItem ( ) ?. getLabel ( ) ) . withContext ( 'active item label' ) . toBe ( 'one' ) ;
168
175
} ) ;
169
176
170
177
describe ( 'Key events' , ( ) => {
@@ -728,6 +735,217 @@ describe('TreeKeyManager', () => {
728
735
} ) ;
729
736
}
730
737
} ) ;
738
+
739
+ describe ( 'typeahead mode' , ( ) => {
740
+ const debounceInterval = 300 ;
741
+
742
+ beforeEach ( ( ) => {
743
+ keyManager = new TreeKeyManager ( {
744
+ items : itemList ,
745
+ typeAheadDebounceInterval : debounceInterval ,
746
+ } ) ;
747
+ } ) ;
748
+
749
+ it ( 'should throw if the items do not implement the getLabel method' , ( ) => {
750
+ const invalidQueryList = new QueryList < any > ( ) ;
751
+ invalidQueryList . reset ( [ { disabled : false } ] ) ;
752
+
753
+ expect (
754
+ ( ) =>
755
+ new TreeKeyManager ( {
756
+ items : invalidQueryList ,
757
+ typeAheadDebounceInterval : true ,
758
+ } ) ,
759
+ ) . toThrowError ( / m u s t i m p l e m e n t / ) ;
760
+ } ) ;
761
+
762
+ it ( 'should debounce the input key presses' , fakeAsync ( ( ) => {
763
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 79 , 'o' ) ) ; // types "o"
764
+ tick ( 1 ) ;
765
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 78 , 'n' ) ) ; // types "n"
766
+ tick ( 1 ) ;
767
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 69 , 'e' ) ) ; // types "e"
768
+
769
+ expect ( keyManager . getActiveItemIndex ( ) )
770
+ . withContext ( 'active item index, before debounce interval' )
771
+ . not . toBe ( 0 ) ;
772
+
773
+ tick ( debounceInterval - 1 ) ;
774
+
775
+ expect ( keyManager . getActiveItemIndex ( ) )
776
+ . withContext ( 'active item index, after partial debounce interval' )
777
+ . not . toBe ( 0 ) ;
778
+
779
+ tick ( 1 ) ;
780
+
781
+ expect ( keyManager . getActiveItemIndex ( ) )
782
+ . withContext ( 'active item index, after full debounce interval' )
783
+ . toBe ( 0 ) ;
784
+ } ) ) ;
785
+
786
+ it ( 'uses a default debounce interval' , fakeAsync ( ( ) => {
787
+ const defaultInterval = 200 ;
788
+ keyManager = new TreeKeyManager ( {
789
+ items : itemList ,
790
+ typeAheadDebounceInterval : true ,
791
+ } ) ;
792
+
793
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 79 , 'o' ) ) ; // types "o"
794
+ tick ( 1 ) ;
795
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 78 , 'n' ) ) ; // types "n"
796
+ tick ( 1 ) ;
797
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 69 , 'e' ) ) ; // types "e"
798
+
799
+ expect ( keyManager . getActiveItemIndex ( ) )
800
+ . withContext ( 'active item index, before debounce interval' )
801
+ . not . toBe ( 0 ) ;
802
+
803
+ tick ( defaultInterval - 1 ) ;
804
+
805
+ expect ( keyManager . getActiveItemIndex ( ) )
806
+ . withContext ( 'active item index, after partial debounce interval' )
807
+ . not . toBe ( 0 ) ;
808
+
809
+ tick ( 1 ) ;
810
+
811
+ expect ( keyManager . getActiveItemIndex ( ) )
812
+ . withContext ( 'active item index, after full debounce interval' )
813
+ . toBe ( 0 ) ;
814
+ } ) ) ;
815
+
816
+ it ( 'should focus the first item that starts with a letter' , fakeAsync ( ( ) => {
817
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 84 , 't' ) ) ; // types "t"
818
+
819
+ tick ( debounceInterval ) ;
820
+
821
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 1 ) ;
822
+ } ) ) ;
823
+
824
+ it ( 'should focus the first item that starts with sequence of letters' , fakeAsync ( ( ) => {
825
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 84 , 't' ) ) ; // types "t"
826
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 72 , 'h' ) ) ; // types "h"
827
+
828
+ tick ( debounceInterval ) ;
829
+
830
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 2 ) ;
831
+ } ) ) ;
832
+
833
+ it ( 'should cancel any pending timers if a navigation key is pressed' , fakeAsync ( ( ) => {
834
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 84 , 't' ) ) ; // types "t"
835
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 72 , 'h' ) ) ; // types "h"
836
+ keyManager . onKeydown ( fakeKeyEvents . downArrow ) ;
837
+
838
+ tick ( debounceInterval ) ;
839
+
840
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 0 ) ;
841
+ } ) ) ;
842
+
843
+ it ( 'should handle non-English input' , fakeAsync ( ( ) => {
844
+ itemList . reset ( [
845
+ new itemParam . constructor ( 'едно' ) ,
846
+ new itemParam . constructor ( 'две' ) ,
847
+ new itemParam . constructor ( 'три' ) ,
848
+ ] ) ;
849
+ itemList . notifyOnChanges ( ) ;
850
+
851
+ const keyboardEvent = createKeyboardEvent ( 'keydown' , 68 , 'д' ) ;
852
+
853
+ keyManager . onKeydown ( keyboardEvent ) ; // types "д"
854
+ tick ( debounceInterval ) ;
855
+
856
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 1 ) ;
857
+ } ) ) ;
858
+
859
+ it ( 'should handle non-letter characters' , fakeAsync ( ( ) => {
860
+ itemList . reset ( [
861
+ new itemParam . constructor ( '[]' ) ,
862
+ new itemParam . constructor ( '321' ) ,
863
+ new itemParam . constructor ( '`!?' ) ,
864
+ ] ) ;
865
+ itemList . notifyOnChanges ( ) ;
866
+
867
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 192 , '`' ) ) ; // types "`"
868
+ tick ( debounceInterval ) ;
869
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 2 ) ;
870
+
871
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 51 , '3' ) ) ; // types "3"
872
+ tick ( debounceInterval ) ;
873
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 1 ) ;
874
+
875
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 219 , '[' ) ) ; // types "["
876
+ tick ( debounceInterval ) ;
877
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 0 ) ;
878
+ } ) ) ;
879
+
880
+ it ( 'should not focus disabled items' , fakeAsync ( ( ) => {
881
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'initial active item index' ) . toBe ( - 1 ) ;
882
+
883
+ parentItem . isDisabled = true ;
884
+
885
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 79 , 'o' ) ) ; // types "o"
886
+ tick ( debounceInterval ) ;
887
+
888
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'initial active item index' ) . toBe ( - 1 ) ;
889
+ } ) ) ;
890
+
891
+ it ( 'should start looking for matches after the active item' , fakeAsync ( ( ) => {
892
+ const frodo = new itemParam . constructor ( 'Frodo' ) ;
893
+ itemList . reset ( [
894
+ new itemParam . constructor ( 'Bilbo' ) ,
895
+ frodo ,
896
+ new itemParam . constructor ( 'Pippin' ) ,
897
+ new itemParam . constructor ( 'Boromir' ) ,
898
+ new itemParam . constructor ( 'Aragorn' ) ,
899
+ ] ) ;
900
+ itemList . notifyOnChanges ( ) ;
901
+
902
+ keyManager . onClick ( frodo ) ;
903
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 66 , 'b' ) ) ;
904
+ tick ( debounceInterval ) ;
905
+
906
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 3 ) ;
907
+ } ) ) ;
908
+
909
+ it ( 'should wrap back around if there were no matches after the active item' , fakeAsync ( ( ) => {
910
+ const boromir = new itemParam . constructor ( 'Boromir' ) ;
911
+ itemList . reset ( [
912
+ new itemParam . constructor ( 'Bilbo' ) ,
913
+ new itemParam . constructor ( 'Frodo' ) ,
914
+ new itemParam . constructor ( 'Pippin' ) ,
915
+ boromir ,
916
+ new itemParam . constructor ( 'Aragorn' ) ,
917
+ ] ) ;
918
+ itemList . notifyOnChanges ( ) ;
919
+
920
+ keyManager . onClick ( boromir ) ;
921
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 66 , 'b' ) ) ;
922
+ tick ( debounceInterval ) ;
923
+
924
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 0 ) ;
925
+ } ) ) ;
926
+
927
+ it ( 'should wrap back around if the last item is active' , fakeAsync ( ( ) => {
928
+ keyManager . onClick ( lastItem ) ;
929
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 79 , 'o' ) ) ;
930
+ tick ( debounceInterval ) ;
931
+
932
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 0 ) ;
933
+ } ) ) ;
934
+
935
+ it ( 'should be able to select the first item' , fakeAsync ( ( ) => {
936
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 79 , 'o' ) ) ;
937
+ tick ( debounceInterval ) ;
938
+
939
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( 0 ) ;
940
+ } ) ) ;
941
+
942
+ it ( 'should not do anything if there is no match' , fakeAsync ( ( ) => {
943
+ keyManager . onKeydown ( createKeyboardEvent ( 'keydown' , 87 , 'w' ) ) ;
944
+ tick ( debounceInterval ) ;
945
+
946
+ expect ( keyManager . getActiveItemIndex ( ) ) . withContext ( 'active item index' ) . toBe ( - 1 ) ;
947
+ } ) ) ;
948
+ } ) ;
731
949
} ) ;
732
950
}
733
951
} ) ;
0 commit comments