@@ -17,153 +17,250 @@ import {
1717} from './key-bindings.js' ;
1818
1919describe ( 'Key bindings controller' , ( ) => {
20- let tag : string ;
21- let instance : LitElement & {
22- key : string ;
23- event : KeyboardEvent ;
24- input : HTMLInputElement ;
25- } ;
26-
27- before ( ( ) => {
28- tag = defineCE (
29- class extends LitElement {
30- public key ?: string ;
31- public event ?: KeyboardEvent ;
32-
33- public get input ( ) {
34- return this . renderRoot . querySelector ( 'input' ) ! ;
35- }
20+ describe ( 'addKeybindings' , ( ) => {
21+ let tag : string ;
22+ let instance : LitElement & {
23+ key : string ;
24+ event : KeyboardEvent ;
25+ input : HTMLInputElement ;
26+ } ;
27+
28+ before ( ( ) => {
29+ tag = defineCE (
30+ class extends LitElement {
31+ public key ?: string ;
32+ public event ?: KeyboardEvent ;
33+
34+ public get input ( ) {
35+ return this . renderRoot . querySelector ( 'input' ) ! ;
36+ }
3637
37- constructor ( ) {
38- super ( ) ;
39- addKeybindings ( this , {
40- skip : ( ) => ! ! this . hidden ,
41- } )
42- . setActivateHandler ( this . handleKeyboardEvent )
43- . set ( 'a' , this . handleKeyboardEvent , { triggers : [ 'keydown' ] } )
44- . set ( 's' , this . handleKeyboardEvent , { triggers : [ 'keyup' ] } )
45- . set ( 'k' , this . handleKeyboardEvent )
46- . set ( 'd' , this . handleKeyboardEvent , {
47- triggers : [ 'keydown' , 'keyup' ] ,
48- preventDefault : true ,
49- stopPropagation : true ,
38+ constructor ( ) {
39+ super ( ) ;
40+ addKeybindings ( this , {
41+ skip : ( ) => ! ! this . hidden ,
5042 } )
51- . set ( [ shiftKey , 'c' ] , this . handleKeyboardEvent )
52- . set ( [ shiftKey , altKey , arrowUp ] , this . handleKeyboardEvent ) ;
43+ . setActivateHandler ( this . handleKeyboardEvent )
44+ . set ( 'a' , this . handleKeyboardEvent , { triggers : [ 'keydown' ] } )
45+ . set ( 's' , this . handleKeyboardEvent , { triggers : [ 'keyup' ] } )
46+ . set ( 'k' , this . handleKeyboardEvent )
47+ . set ( 'd' , this . handleKeyboardEvent , {
48+ triggers : [ 'keydown' , 'keyup' ] ,
49+ preventDefault : true ,
50+ stopPropagation : true ,
51+ } )
52+ . set ( [ shiftKey , 'c' ] , this . handleKeyboardEvent )
53+ . set ( [ shiftKey , altKey , arrowUp ] , this . handleKeyboardEvent ) ;
5354
54- addKeybindings ( this ) . set ( 'x' , this . handleKeyboardEvent ) ;
55- }
55+ addKeybindings ( this ) . set ( 'x' , this . handleKeyboardEvent ) ;
56+ }
5657
57- private handleKeyboardEvent ( event : KeyboardEvent ) {
58- this . key = event . key ;
59- this . event = event ;
60- }
58+ private handleKeyboardEvent ( event : KeyboardEvent ) {
59+ this . key = event . key ;
60+ this . event = event ;
61+ }
6162
62- protected override render ( ) {
63- return html `< input /> ` ;
63+ protected override render ( ) {
64+ return html `< input /> ` ;
65+ }
6466 }
65- }
66- ) ;
67- } ) ;
67+ ) ;
68+ } ) ;
6869
69- function dispatch ( node : Element , type : 'keydown' | 'keyup' , key : string ) {
70- node . dispatchEvent (
71- new KeyboardEvent ( type , {
72- key,
73- bubbles : true ,
74- composed : true ,
75- cancelable : true ,
76- } )
77- ) ;
78- }
79-
80- beforeEach ( async ( ) => {
81- const tagName = unsafeStatic ( tag ) ;
82- instance = await fixture ( html `< ${ tagName } > </ ${ tagName } > ` ) ;
83- } ) ;
70+ beforeEach ( async ( ) => {
71+ const tagName = unsafeStatic ( tag ) ;
72+ instance = await fixture ( html `< ${ tagName } > </ ${ tagName } > ` ) ;
73+ } ) ;
8474
85- describe ( 'Triggers' , ( ) => {
86- it ( 'keydown - keydown event works' , async ( ) => {
87- dispatch ( instance , 'keydown' , 'a' ) ;
88- expect ( instance . key ) . to . equal ( 'a' ) ;
89- expect ( instance . event . type ) . to . equal ( 'keydown' ) ;
75+ describe ( 'Triggers' , ( ) => {
76+ it ( 'keydown - keydown event works' , async ( ) => {
77+ dispatch ( instance , 'keydown' , 'a' ) ;
78+ expect ( instance . key ) . to . equal ( 'a' ) ;
79+ expect ( instance . event . type ) . to . equal ( 'keydown' ) ;
80+ } ) ;
81+
82+ it ( 'keydown - keyup event is ignored' , async ( ) => {
83+ dispatch ( instance , 'keyup' , 'a' ) ;
84+ expect ( instance . key ) . to . be . undefined ;
85+ expect ( instance . event ) . to . be . undefined ;
86+ } ) ;
87+
88+ it ( 'keyup - keyup event works' , async ( ) => {
89+ dispatch ( instance , 'keyup' , 's' ) ;
90+ expect ( instance . key ) . to . equal ( 's' ) ;
91+ expect ( instance . event . type ) . to . equal ( 'keyup' ) ;
92+ } ) ;
93+
94+ it ( 'keyup - keydown event is ignored' , async ( ) => {
95+ dispatch ( instance , 'keydown' , 's' ) ;
96+ expect ( instance . key ) . to . be . undefined ;
97+ expect ( instance . event ) . to . be . undefined ;
98+ } ) ;
9099 } ) ;
91100
92- it ( 'keydown - keyup event is ignored' , async ( ) => {
93- dispatch ( instance , 'keyup' , 'a ') ;
101+ it ( 'should honor default skip condition' , ( ) => {
102+ simulateKeyboard ( instance . input , 'x ' ) ;
94103 expect ( instance . key ) . to . be . undefined ;
95104 expect ( instance . event ) . to . be . undefined ;
96105 } ) ;
97106
98- it ( 'keyup - keyup event works' , async ( ) => {
99- dispatch ( instance , 'keyup' , 's' ) ;
100- expect ( instance . key ) . to . equal ( 's' ) ;
107+ it ( 'should honor skip condition' , ( ) => {
108+ instance . hidden = true ;
109+
110+ simulateKeyboard ( instance , 'k' ) ;
111+ expect ( instance . key ) . to . be . undefined ;
112+ expect ( instance . event ) . to . be . undefined ;
113+ } ) ;
114+
115+ it ( 'event handler modifiers - prevent default' , ( ) => {
116+ dispatch ( instance , 'keydown' , 'd' ) ;
117+ expect ( instance . event . type ) . to . equal ( 'keydown' ) ;
118+ expect ( instance . event . defaultPrevented ) . to . be . true ;
119+
120+ dispatch ( instance , 'keyup' , 'd' ) ;
101121 expect ( instance . event . type ) . to . equal ( 'keyup' ) ;
122+ expect ( instance . event . defaultPrevented ) . to . be . true ;
123+ } ) ;
124+
125+ it ( 'event handler modifiers - stop propagation' , ( ) => {
126+ let parentHandlerCalled = false ;
127+ const handler = ( ) => {
128+ parentHandlerCalled = true ;
129+ } ;
130+ const container = instance . parentElement ! ;
131+ container . addEventListener ( 'keydown' , handler , { once : true } ) ;
132+ container . addEventListener ( 'keyup' , handler , { once : true } ) ;
133+
134+ simulateKeyboard ( instance , 'd' ) ;
135+ expect ( parentHandlerCalled ) . to . be . false ;
136+ expect ( instance . key ) . to . equal ( 'd' ) ;
137+ } ) ;
138+
139+ it ( 'activation keys' , ( ) => {
140+ for ( const key of [ enterKey , spaceBar ] ) {
141+ simulateKeyboard ( instance , key ) ;
142+ expect ( instance . key ) . to . equal ( key . toLowerCase ( ) ) ;
143+ }
102144 } ) ;
103145
104- it ( 'keyup - keydown event is ignored' , async ( ) => {
105- dispatch ( instance , 'keydown' , 's' ) ;
146+ it ( 'combinations' , ( ) => {
147+ simulateKeyboard ( instance , shiftKey ) ;
106148 expect ( instance . key ) . to . be . undefined ;
107- expect ( instance . event ) . to . be . undefined ;
149+
150+ simulateKeyboard ( instance , 'c' ) ;
151+ expect ( instance . key ) . to . be . undefined ;
152+
153+ simulateKeyboard ( instance , [ shiftKey , 'c' ] ) ;
154+ expect ( instance . key ) . to . equal ( 'c' ) ;
155+
156+ simulateKeyboard ( instance , [ shiftKey , altKey , arrowUp ] ) ;
157+ expect ( instance . key ) . to . equal ( arrowUp . toLowerCase ( ) ) ;
108158 } ) ;
109159 } ) ;
110160
111- it ( 'should honor default skip condition' , ( ) => {
112- simulateKeyboard ( instance . input , 'x' ) ;
113- expect ( instance . key ) . to . be . undefined ;
114- expect ( instance . event ) . to . be . undefined ;
115- } ) ;
161+ describe ( 'multi non-modifier key combinations' , ( ) => {
162+ let multiTag : string ;
163+ let multiInstance : LitElement & { callCount : number } ;
116164
117- it ( 'should honor skip condition' , ( ) => {
118- instance . hidden = true ;
165+ before ( ( ) => {
166+ multiTag = defineCE (
167+ class extends LitElement {
168+ public callCount = 0 ;
119169
120- simulateKeyboard ( instance , 'k' ) ;
121- expect ( instance . key ) . to . be . undefined ;
122- expect ( instance . event ) . to . be . undefined ;
123- } ) ;
170+ constructor ( ) {
171+ super ( ) ;
172+ addKeybindings ( this ) . set ( [ 'x' , 'z' ] , ( ) => this . callCount ++ ) ;
173+ }
174+ }
175+ ) ;
176+ } ) ;
124177
125- it ( 'event handler modifiers - prevent default' , ( ) => {
126- dispatch ( instance , 'keydown' , 'd' ) ;
127- expect ( instance . event . type ) . to . equal ( 'keydown' ) ;
128- expect ( instance . event . defaultPrevented ) . to . be . true ;
178+ beforeEach ( async ( ) => {
179+ const tagName = unsafeStatic ( multiTag ) ;
180+ multiInstance = await fixture ( html ` < ${ tagName } > </ ${ tagName } > ` ) ;
181+ } ) ;
129182
130- dispatch ( instance , 'keyup' , 'd' ) ;
131- expect ( instance . event . type ) . to . equal ( 'keyup' ) ;
132- expect ( instance . event . defaultPrevented ) . to . be . true ;
133- } ) ;
183+ it ( 'should not fire when only one of the keys is pressed' , ( ) => {
184+ dispatch ( multiInstance , 'keydown' , 'x' ) ;
185+ expect ( multiInstance . callCount ) . to . equal ( 0 ) ;
134186
135- it ( 'event handler modifiers - stop propagation' , ( ) => {
136- let parentHandlerCalled = false ;
137- const handler = ( ) => {
138- parentHandlerCalled = true ;
139- } ;
140- const container = instance . parentElement ! ;
141- container . addEventListener ( 'keydown' , handler , { once : true } ) ;
142- container . addEventListener ( 'keyup' , handler , { once : true } ) ;
187+ dispatch ( multiInstance , 'keyup' , 'x' ) ;
143188
144- simulateKeyboard ( instance , 'd' ) ;
145- expect ( parentHandlerCalled ) . to . be . false ;
146- expect ( instance . key ) . to . equal ( 'd' ) ;
147- } ) ;
189+ dispatch ( multiInstance , 'keydown' , 'z' ) ;
190+ expect ( multiInstance . callCount ) . to . equal ( 0 ) ;
191+ } ) ;
148192
149- it ( 'activation keys' , ( ) => {
150- for ( const key of [ enterKey , spaceBar ] ) {
151- simulateKeyboard ( instance , key ) ;
152- expect ( instance . key ) . to . equal ( key . toLowerCase ( ) ) ;
153- }
193+ it ( 'should fire when all keys are held simultaneously' , ( ) => {
194+ dispatch ( multiInstance , 'keydown' , 'x' ) ;
195+ dispatch ( multiInstance , 'keydown' , 'z' ) ;
196+ expect ( multiInstance . callCount ) . to . equal ( 1 ) ;
197+ } ) ;
198+
199+ it ( 'should clear pressed keys on window blur' , ( ) => {
200+ // Hold 'x', then switch away without releasing it
201+ dispatch ( multiInstance , 'keydown' , 'x' ) ;
202+ window . dispatchEvent ( new FocusEvent ( 'blur' ) ) ;
203+
204+ // 'x' should no longer be tracked; pressing 'z' alone should not fire
205+ dispatch ( multiInstance , 'keydown' , 'z' ) ;
206+ expect ( multiInstance . callCount ) . to . equal ( 0 ) ;
207+ } ) ;
154208 } ) ;
155209
156- it ( 'combinations' , ( ) => {
157- simulateKeyboard ( instance , shiftKey ) ;
158- expect ( instance . key ) . to . be . undefined ;
210+ describe ( 'repeat option' , ( ) => {
211+ let repeatTag : string ;
212+ let repeatInstance : LitElement & { callCount : number } ;
213+
214+ before ( ( ) => {
215+ repeatTag = defineCE (
216+ class extends LitElement {
217+ public callCount = 0 ;
218+
219+ constructor ( ) {
220+ super ( ) ;
221+ addKeybindings ( this )
222+ . set ( 'a' , ( ) => this . callCount ++ )
223+ . set ( 'b' , ( ) => this . callCount ++ , { repeat : true } ) ;
224+ }
225+ }
226+ ) ;
227+ } ) ;
228+
229+ beforeEach ( async ( ) => {
230+ const tagName = unsafeStatic ( repeatTag ) ;
231+ repeatInstance = await fixture ( html `< ${ tagName } > </ ${ tagName } > ` ) ;
232+ } ) ;
159233
160- simulateKeyboard ( instance , 'c' ) ;
161- expect ( instance . key ) . to . be . undefined ;
234+ it ( 'should not fire on repeated keydown by default' , ( ) => {
235+ dispatch ( repeatInstance , 'keydown' , 'a' , true ) ;
236+ expect ( repeatInstance . callCount ) . to . equal ( 0 ) ;
237+ } ) ;
162238
163- simulateKeyboard ( instance , [ shiftKey , 'c' ] ) ;
164- expect ( instance . key ) . to . equal ( 'c' ) ;
239+ it ( 'should fire on initial keydown regardless of repeat option' , ( ) => {
240+ dispatch ( repeatInstance , 'keydown' , 'b' ) ;
241+ expect ( repeatInstance . callCount ) . to . equal ( 1 ) ;
242+ } ) ;
165243
166- simulateKeyboard ( instance , [ shiftKey , altKey , arrowUp ] ) ;
167- expect ( instance . key ) . to . equal ( arrowUp . toLowerCase ( ) ) ;
244+ it ( 'should fire on repeated keydown when repeat is enabled' , ( ) => {
245+ dispatch ( repeatInstance , 'keydown' , 'b' , true ) ;
246+ expect ( repeatInstance . callCount ) . to . equal ( 1 ) ;
247+ } ) ;
168248 } ) ;
169249} ) ;
250+
251+ function dispatch (
252+ node : Element ,
253+ type : 'keydown' | 'keyup' ,
254+ key : string ,
255+ repeat = false
256+ ) {
257+ node . dispatchEvent (
258+ new KeyboardEvent ( type , {
259+ key,
260+ bubbles : true ,
261+ composed : true ,
262+ cancelable : true ,
263+ repeat,
264+ } )
265+ ) ;
266+ }
0 commit comments