]>
Commit | Line | Data |
---|---|---|
f00b6fb6 | 1 | var assert = chai.assert; |
2 | var expect = chai.expect; | |
3 | ||
dfae3209 SR |
4 | import keysyms from '../core/input/keysymdef.js'; |
5 | import * as KeyboardUtil from '../core/input/util.js'; | |
6 | ||
31f169e8 | 7 | /* jshint newcap: false, expr: true */ |
f00b6fb6 | 8 | describe('Key Event Pipeline Stages', function() { |
9 | "use strict"; | |
10 | describe('Decode Keyboard Events', function() { | |
11 | it('should pass events to the next stage', function(done) { | |
ae510306 | 12 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 13 | expect(evt).to.be.an.object; |
14 | done(); | |
15 | }).keydown({keyCode: 0x41}); | |
16 | }); | |
17 | it('should pass the right keysym through', function(done) { | |
ae510306 | 18 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 19 | expect(evt.keysym).to.be.deep.equal(keysyms.lookup(0x61)); |
20 | done(); | |
21 | }).keypress({keyCode: 0x41}); | |
22 | }); | |
23 | it('should pass the right keyid through', function(done) { | |
ae510306 | 24 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 25 | expect(evt).to.have.property('keyId', 0x41); |
26 | done(); | |
27 | }).keydown({keyCode: 0x41}); | |
28 | }); | |
29 | it('should not sync modifiers on a keypress', function() { | |
30 | // Firefox provides unreliable modifier state on keypress events | |
31 | var count = 0; | |
ae510306 | 32 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 33 | ++count; |
34 | }).keypress({keyCode: 0x41, ctrlKey: true}); | |
35 | expect(count).to.be.equal(1); | |
36 | }); | |
37 | it('should sync modifiers if necessary', function(done) { | |
38 | var count = 0; | |
ae510306 | 39 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 40 | switch (count) { |
41 | case 0: // fake a ctrl keydown | |
42 | expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xffe3), type: 'keydown'}); | |
43 | ++count; | |
44 | break; | |
45 | case 1: | |
46 | expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown', keysym: keysyms.lookup(0x61)}); | |
47 | done(); | |
48 | break; | |
49 | } | |
50 | }).keydown({keyCode: 0x41, ctrlKey: true}); | |
51 | }); | |
52 | it('should forward keydown events with the right type', function(done) { | |
ae510306 | 53 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 54 | expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'}); |
55 | done(); | |
31f169e8 | 56 | }).keydown({keyCode: 0x41}); |
f00b6fb6 | 57 | }); |
58 | it('should forward keyup events with the right type', function(done) { | |
ae510306 | 59 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 60 | expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'}); |
61 | done(); | |
62 | }).keyup({keyCode: 0x41}); | |
63 | }); | |
64 | it('should forward keypress events with the right type', function(done) { | |
ae510306 | 65 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 66 | expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'}); |
67 | done(); | |
68 | }).keypress({keyCode: 0x41}); | |
69 | }); | |
70 | it('should generate stalls if a char modifier is down while a key is pressed', function(done) { | |
71 | var count = 0; | |
ae510306 | 72 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { |
f00b6fb6 | 73 | switch (count) { |
74 | case 0: // fake altgr | |
75 | expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xfe03), type: 'keydown'}); | |
76 | ++count; | |
77 | break; | |
78 | case 1: // stall before processing the 'a' keydown | |
79 | expect(evt).to.be.deep.equal({type: 'stall'}); | |
80 | ++count; | |
81 | break; | |
82 | case 2: // 'a' | |
83 | expect(evt).to.be.deep.equal({ | |
84 | type: 'keydown', | |
85 | keyId: 0x41, | |
86 | keysym: keysyms.lookup(0x61) | |
87 | }); | |
88 | ||
89 | done(); | |
90 | break; | |
91 | } | |
92 | }).keydown({keyCode: 0x41, altGraphKey: true}); | |
93 | ||
94 | }); | |
95 | describe('suppress the right events at the right time', function() { | |
96 | it('should suppress anything while a shortcut modifier is down', function() { | |
ae510306 | 97 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); |
f00b6fb6 | 98 | |
99 | obj.keydown({keyCode: 0x11}); // press ctrl | |
100 | expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.true; | |
101 | expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true; | |
102 | expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.true; | |
103 | expect(obj.keydown({keyCode: 0x3c})).to.be.true; // < key on DK Windows | |
104 | expect(obj.keydown({keyCode: 0xde})).to.be.true; // Ø key on DK | |
105 | }); | |
106 | it('should suppress non-character keys', function() { | |
ae510306 | 107 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); |
f00b6fb6 | 108 | |
109 | expect(obj.keydown({keyCode: 0x08}), 'a').to.be.true; | |
110 | expect(obj.keydown({keyCode: 0x09}), 'b').to.be.true; | |
111 | expect(obj.keydown({keyCode: 0x11}), 'd').to.be.true; | |
112 | expect(obj.keydown({keyCode: 0x12}), 'e').to.be.true; | |
113 | }); | |
114 | it('should not suppress shift', function() { | |
ae510306 | 115 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); |
f00b6fb6 | 116 | |
117 | expect(obj.keydown({keyCode: 0x10}), 'd').to.be.false; | |
118 | }); | |
119 | it('should generate event for shift keydown', function() { | |
120 | var called = false; | |
ae510306 | 121 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 122 | expect(evt).to.have.property('keysym'); |
123 | called = true; | |
124 | }).keydown({keyCode: 0x10}); | |
125 | expect(called).to.be.true; | |
126 | }); | |
127 | it('should not suppress character keys', function() { | |
ae510306 | 128 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); |
f00b6fb6 | 129 | |
130 | expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false; | |
131 | expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; | |
132 | expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false; | |
133 | expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows | |
134 | expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK | |
135 | }); | |
136 | it('should not suppress if a char modifier is down', function() { | |
ae510306 | 137 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {}); |
f00b6fb6 | 138 | |
139 | obj.keydown({keyCode: 0xe1}); // press altgr | |
140 | expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false; | |
141 | expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; | |
142 | expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false; | |
143 | expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows | |
144 | expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK | |
145 | }); | |
146 | }); | |
147 | describe('Keypress and keyup events', function() { | |
148 | it('should always suppress event propagation', function() { | |
ae510306 | 149 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); |
f00b6fb6 | 150 | |
151 | expect(obj.keypress({keyCode: 'A'.charCodeAt()})).to.be.true; | |
152 | expect(obj.keypress({keyCode: 0x3c})).to.be.true; // < key on DK Windows | |
153 | expect(obj.keypress({keyCode: 0x11})).to.be.true; | |
154 | ||
155 | expect(obj.keyup({keyCode: 'A'.charCodeAt()})).to.be.true; | |
156 | expect(obj.keyup({keyCode: 0x3c})).to.be.true; // < key on DK Windows | |
157 | expect(obj.keyup({keyCode: 0x11})).to.be.true; | |
158 | }); | |
159 | it('should never generate stalls', function() { | |
ae510306 | 160 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 161 | expect(evt.type).to.not.be.equal('stall'); |
162 | }); | |
163 | ||
164 | obj.keypress({keyCode: 'A'.charCodeAt()}); | |
165 | obj.keypress({keyCode: 0x3c}); | |
166 | obj.keypress({keyCode: 0x11}); | |
167 | ||
168 | obj.keyup({keyCode: 'A'.charCodeAt()}); | |
169 | obj.keyup({keyCode: 0x3c}); | |
170 | obj.keyup({keyCode: 0x11}); | |
171 | }); | |
172 | }); | |
173 | describe('mark events if a char modifier is down', function() { | |
174 | it('should not mark modifiers on a keydown event', function() { | |
175 | var times_called = 0; | |
ae510306 | 176 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { |
f00b6fb6 | 177 | switch (times_called++) { |
178 | case 0: //altgr | |
179 | break; | |
180 | case 1: // 'a' | |
181 | expect(evt).to.not.have.property('escape'); | |
182 | break; | |
183 | } | |
184 | }); | |
185 | ||
186 | obj.keydown({keyCode: 0xe1}); // press altgr | |
187 | obj.keydown({keyCode: 'A'.charCodeAt()}); | |
188 | }); | |
189 | ||
190 | it('should indicate on events if a single-key char modifier is down', function(done) { | |
191 | var times_called = 0; | |
ae510306 | 192 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { |
f00b6fb6 | 193 | switch (times_called++) { |
194 | case 0: //altgr | |
195 | break; | |
196 | case 1: // 'a' | |
197 | expect(evt).to.be.deep.equal({ | |
198 | type: 'keypress', | |
199 | keyId: 'A'.charCodeAt(), | |
200 | keysym: keysyms.lookup('a'.charCodeAt()), | |
201 | escape: [0xfe03] | |
202 | }); | |
203 | done(); | |
204 | return; | |
205 | } | |
206 | }); | |
207 | ||
208 | obj.keydown({keyCode: 0xe1}); // press altgr | |
209 | obj.keypress({keyCode: 'A'.charCodeAt()}); | |
210 | }); | |
211 | it('should indicate on events if a multi-key char modifier is down', function(done) { | |
212 | var times_called = 0; | |
ae510306 | 213 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xffe9, 0xffe3]), function(evt) { |
f00b6fb6 | 214 | switch (times_called++) { |
215 | case 0: //ctrl | |
216 | break; | |
217 | case 1: //alt | |
218 | break; | |
219 | case 2: // 'a' | |
220 | expect(evt).to.be.deep.equal({ | |
221 | type: 'keypress', | |
222 | keyId: 'A'.charCodeAt(), | |
223 | keysym: keysyms.lookup('a'.charCodeAt()), | |
224 | escape: [0xffe9, 0xffe3] | |
225 | }); | |
226 | done(); | |
227 | return; | |
228 | } | |
229 | }); | |
230 | ||
231 | obj.keydown({keyCode: 0x11}); // press ctrl | |
232 | obj.keydown({keyCode: 0x12}); // press alt | |
233 | obj.keypress({keyCode: 'A'.charCodeAt()}); | |
234 | }); | |
235 | it('should not consider a char modifier to be down on the modifier key itself', function() { | |
ae510306 | 236 | var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { |
f00b6fb6 | 237 | expect(evt).to.not.have.property('escape'); |
238 | }); | |
239 | ||
240 | obj.keydown({keyCode: 0xe1}); // press altgr | |
241 | ||
242 | }); | |
243 | }); | |
244 | describe('add/remove keysym', function() { | |
245 | it('should remove keysym from keydown if a char key and no modifier', function() { | |
ae510306 | 246 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 247 | expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'}); |
248 | }).keydown({keyCode: 0x41}); | |
249 | }); | |
250 | it('should not remove keysym from keydown if a shortcut modifier is down', function() { | |
251 | var times_called = 0; | |
ae510306 | 252 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 253 | switch (times_called++) { |
254 | case 1: | |
255 | expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'}); | |
256 | break; | |
257 | } | |
258 | }).keydown({keyCode: 0x41, ctrlKey: true}); | |
259 | expect(times_called).to.be.equal(2); | |
260 | }); | |
261 | it('should not remove keysym from keydown if a char modifier is down', function() { | |
262 | var times_called = 0; | |
ae510306 | 263 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { |
f00b6fb6 | 264 | switch (times_called++) { |
265 | case 2: | |
266 | expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'}); | |
267 | break; | |
268 | } | |
269 | }).keydown({keyCode: 0x41, altGraphKey: true}); | |
270 | expect(times_called).to.be.equal(3); | |
271 | }); | |
272 | it('should not remove keysym from keydown if key is noncharacter', function() { | |
ae510306 | 273 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 274 | expect(evt, 'bacobjpace').to.be.deep.equal({keyId: 0x09, keysym: keysyms.lookup(0xff09), type: 'keydown'}); |
275 | }).keydown({keyCode: 0x09}); | |
276 | ||
ae510306 | 277 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 278 | expect(evt, 'ctrl').to.be.deep.equal({keyId: 0x11, keysym: keysyms.lookup(0xffe3), type: 'keydown'}); |
279 | }).keydown({keyCode: 0x11}); | |
280 | }); | |
281 | it('should never remove keysym from keypress', function() { | |
ae510306 | 282 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 283 | expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'}); |
284 | }).keypress({keyCode: 0x41}); | |
285 | }); | |
286 | it('should never remove keysym from keyup', function() { | |
ae510306 | 287 | KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { |
f00b6fb6 | 288 | expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'}); |
289 | }).keyup({keyCode: 0x41}); | |
290 | }); | |
291 | }); | |
292 | // on keypress, keyup(?), always set keysym | |
293 | // on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down | |
294 | }); | |
295 | ||
296 | describe('Verify that char modifiers are active', function() { | |
297 | it('should pass keydown events through if there is no stall', function(done) { | |
ae510306 | 298 | var obj = KeyboardUtil.VerifyCharModifier(function(evt){ |
f00b6fb6 | 299 | expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)}); |
300 | done(); | |
301 | })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)}); | |
302 | }); | |
303 | it('should pass keyup events through if there is no stall', function(done) { | |
ae510306 | 304 | var obj = KeyboardUtil.VerifyCharModifier(function(evt){ |
f00b6fb6 | 305 | expect(evt).to.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)}); |
306 | done(); | |
307 | })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)}); | |
308 | }); | |
309 | it('should pass keypress events through if there is no stall', function(done) { | |
ae510306 | 310 | var obj = KeyboardUtil.VerifyCharModifier(function(evt){ |
f00b6fb6 | 311 | expect(evt).to.deep.equal({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)}); |
312 | done(); | |
313 | })({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)}); | |
314 | }); | |
315 | it('should not pass stall events through', function(done){ | |
ae510306 | 316 | var obj = KeyboardUtil.VerifyCharModifier(function(evt){ |
f00b6fb6 | 317 | // should only be called once, for the keydown |
318 | expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)}); | |
319 | done(); | |
320 | }); | |
321 | ||
322 | obj({type: 'stall'}); | |
323 | obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)}); | |
324 | }); | |
325 | it('should merge keydown and keypress events if they come after a stall', function(done) { | |
326 | var next_called = false; | |
ae510306 | 327 | var obj = KeyboardUtil.VerifyCharModifier(function(evt){ |
f00b6fb6 | 328 | // should only be called once, for the keydown |
329 | expect(next_called).to.be.false; | |
330 | next_called = true; | |
331 | expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44)}); | |
332 | done(); | |
333 | }); | |
334 | ||
335 | obj({type: 'stall'}); | |
336 | obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
337 | obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)}); | |
338 | expect(next_called).to.be.false; | |
339 | }); | |
340 | it('should preserve modifier attribute when merging if keysyms differ', function(done) { | |
341 | var next_called = false; | |
ae510306 | 342 | var obj = KeyboardUtil.VerifyCharModifier(function(evt){ |
f00b6fb6 | 343 | // should only be called once, for the keydown |
344 | expect(next_called).to.be.false; | |
345 | next_called = true; | |
346 | expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44), escape: [0xffe3]}); | |
347 | done(); | |
348 | }); | |
349 | ||
350 | obj({type: 'stall'}); | |
351 | obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
352 | obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44), escape: [0xffe3]}); | |
353 | expect(next_called).to.be.false; | |
354 | }); | |
355 | it('should not preserve modifier attribute when merging if keysyms are the same', function() { | |
ae510306 | 356 | var obj = KeyboardUtil.VerifyCharModifier(function(evt){ |
f00b6fb6 | 357 | expect(evt).to.not.have.property('escape'); |
358 | }); | |
359 | ||
360 | obj({type: 'stall'}); | |
361 | obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
362 | obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x42), escape: [0xffe3]}); | |
363 | }); | |
364 | it('should not merge keydown and keypress events if there is no stall', function(done) { | |
365 | var times_called = 0; | |
ae510306 | 366 | var obj = KeyboardUtil.VerifyCharModifier(function(evt){ |
f00b6fb6 | 367 | switch(times_called) { |
368 | case 0: | |
369 | expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
370 | break; | |
371 | case 1: | |
372 | expect(evt).to.deep.equal({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)}); | |
373 | done(); | |
374 | break; | |
375 | } | |
376 | ||
377 | ++times_called; | |
378 | }); | |
379 | ||
380 | obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
381 | obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)}); | |
382 | }); | |
383 | it('should not merge keydown and keypress events if separated by another event', function(done) { | |
384 | var times_called = 0; | |
ae510306 | 385 | var obj = KeyboardUtil.VerifyCharModifier(function(evt){ |
f00b6fb6 | 386 | switch(times_called) { |
387 | case 0: | |
388 | expect(evt,1).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
389 | break; | |
390 | case 1: | |
391 | expect(evt,2).to.deep.equal({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)}); | |
392 | break; | |
393 | case 2: | |
394 | expect(evt,3).to.deep.equal({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)}); | |
395 | done(); | |
396 | break; | |
397 | } | |
398 | ||
399 | ++times_called; | |
400 | }); | |
401 | ||
402 | obj({type: 'stall'}); | |
403 | obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
404 | obj({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)}); | |
405 | obj({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)}); | |
406 | }); | |
407 | }); | |
408 | ||
409 | describe('Track Key State', function() { | |
410 | it('should do nothing on keyup events if no keys are down', function() { | |
ae510306 | 411 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 412 | expect(true).to.be.false; |
413 | }); | |
414 | obj({type: 'keyup', keyId: 0x41}); | |
415 | }); | |
416 | it('should insert into the queue on keydown if no keys are down', function() { | |
417 | var times_called = 0; | |
418 | var elem = null; | |
419 | var keysymsdown = {}; | |
ae510306 | 420 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 421 | ++times_called; |
422 | if (elem.type == 'keyup') { | |
423 | expect(evt).to.have.property('keysym'); | |
424 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
425 | delete keysymsdown[evt.keysym.keysym]; | |
426 | } | |
427 | else { | |
428 | expect(evt).to.be.deep.equal(elem); | |
429 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
430 | } | |
431 | elem = null; | |
432 | }); | |
433 | ||
434 | expect(elem).to.be.null; | |
435 | elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}; | |
436 | keysymsdown[keysyms.lookup(0x42).keysym] = true; | |
437 | obj(elem); | |
438 | expect(elem).to.be.null; | |
439 | elem = {type: 'keyup', keyId: 0x41}; | |
440 | obj(elem); | |
441 | expect(elem).to.be.null; | |
442 | expect(times_called).to.be.equal(2); | |
443 | }); | |
444 | it('should insert into the queue on keypress if no keys are down', function() { | |
445 | var times_called = 0; | |
446 | var elem = null; | |
447 | var keysymsdown = {}; | |
ae510306 | 448 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 449 | ++times_called; |
450 | if (elem.type == 'keyup') { | |
451 | expect(evt).to.have.property('keysym'); | |
452 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
453 | delete keysymsdown[evt.keysym.keysym]; | |
454 | } | |
455 | else { | |
456 | expect(evt).to.be.deep.equal(elem); | |
457 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
458 | } | |
459 | elem = null; | |
460 | }); | |
461 | ||
462 | expect(elem).to.be.null; | |
463 | elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)}; | |
464 | keysymsdown[keysyms.lookup(0x42).keysym] = true; | |
465 | obj(elem); | |
466 | expect(elem).to.be.null; | |
467 | elem = {type: 'keyup', keyId: 0x41}; | |
468 | obj(elem); | |
469 | expect(elem).to.be.null; | |
470 | expect(times_called).to.be.equal(2); | |
471 | }); | |
472 | it('should add keysym to last key entry if keyId matches', function() { | |
473 | // this implies that a single keyup will release both keysyms | |
474 | var times_called = 0; | |
475 | var elem = null; | |
476 | var keysymsdown = {}; | |
ae510306 | 477 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 478 | ++times_called; |
479 | if (elem.type == 'keyup') { | |
480 | expect(evt).to.have.property('keysym'); | |
481 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
482 | delete keysymsdown[evt.keysym.keysym]; | |
483 | } | |
484 | else { | |
485 | expect(evt).to.be.deep.equal(elem); | |
486 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
487 | elem = null; | |
488 | } | |
489 | }); | |
490 | ||
491 | expect(elem).to.be.null; | |
492 | elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)}; | |
493 | keysymsdown[keysyms.lookup(0x42).keysym] = true; | |
494 | obj(elem); | |
495 | expect(elem).to.be.null; | |
496 | elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)}; | |
497 | keysymsdown[keysyms.lookup(0x43).keysym] = true; | |
498 | obj(elem); | |
499 | expect(elem).to.be.null; | |
500 | elem = {type: 'keyup', keyId: 0x41}; | |
501 | obj(elem); | |
502 | expect(times_called).to.be.equal(4); | |
503 | }); | |
504 | it('should create new key entry if keyId matches and keysym does not', function() { | |
505 | // this implies that a single keyup will release both keysyms | |
506 | var times_called = 0; | |
507 | var elem = null; | |
508 | var keysymsdown = {}; | |
ae510306 | 509 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 510 | ++times_called; |
511 | if (elem.type == 'keyup') { | |
512 | expect(evt).to.have.property('keysym'); | |
513 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
514 | delete keysymsdown[evt.keysym.keysym]; | |
515 | } | |
516 | else { | |
517 | expect(evt).to.be.deep.equal(elem); | |
518 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
519 | elem = null; | |
520 | } | |
521 | }); | |
522 | ||
523 | expect(elem).to.be.null; | |
524 | elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)}; | |
525 | keysymsdown[keysyms.lookup(0x42).keysym] = true; | |
526 | obj(elem); | |
527 | expect(elem).to.be.null; | |
528 | elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)}; | |
529 | keysymsdown[keysyms.lookup(0x43).keysym] = true; | |
530 | obj(elem); | |
531 | expect(times_called).to.be.equal(2); | |
532 | expect(elem).to.be.null; | |
533 | elem = {type: 'keyup', keyId: 0}; | |
534 | obj(elem); | |
535 | expect(times_called).to.be.equal(3); | |
536 | elem = {type: 'keyup', keyId: 0}; | |
537 | obj(elem); | |
538 | expect(times_called).to.be.equal(4); | |
539 | }); | |
540 | it('should merge key entry if keyIds are zero and keysyms match', function() { | |
541 | // this implies that a single keyup will release both keysyms | |
542 | var times_called = 0; | |
543 | var elem = null; | |
544 | var keysymsdown = {}; | |
ae510306 | 545 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 546 | ++times_called; |
547 | if (elem.type == 'keyup') { | |
548 | expect(evt).to.have.property('keysym'); | |
549 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
550 | delete keysymsdown[evt.keysym.keysym]; | |
551 | } | |
552 | else { | |
553 | expect(evt).to.be.deep.equal(elem); | |
554 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
555 | elem = null; | |
556 | } | |
557 | }); | |
558 | ||
559 | expect(elem).to.be.null; | |
560 | elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)}; | |
561 | keysymsdown[keysyms.lookup(0x42).keysym] = true; | |
562 | obj(elem); | |
563 | expect(elem).to.be.null; | |
564 | elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)}; | |
565 | keysymsdown[keysyms.lookup(0x42).keysym] = true; | |
566 | obj(elem); | |
567 | expect(times_called).to.be.equal(2); | |
568 | expect(elem).to.be.null; | |
569 | elem = {type: 'keyup', keyId: 0}; | |
570 | obj(elem); | |
571 | expect(times_called).to.be.equal(3); | |
572 | }); | |
573 | it('should add keysym as separate entry if keyId does not match last event', function() { | |
574 | // this implies that separate keyups are required | |
575 | var times_called = 0; | |
576 | var elem = null; | |
577 | var keysymsdown = {}; | |
ae510306 | 578 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 579 | ++times_called; |
580 | if (elem.type == 'keyup') { | |
581 | expect(evt).to.have.property('keysym'); | |
582 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
583 | delete keysymsdown[evt.keysym.keysym]; | |
584 | } | |
585 | else { | |
586 | expect(evt).to.be.deep.equal(elem); | |
587 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
588 | elem = null; | |
589 | } | |
590 | }); | |
591 | ||
592 | expect(elem).to.be.null; | |
593 | elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)}; | |
594 | keysymsdown[keysyms.lookup(0x42).keysym] = true; | |
595 | obj(elem); | |
596 | expect(elem).to.be.null; | |
597 | elem = {type: 'keypress', keyId: 0x42, keysym: keysyms.lookup(0x43)}; | |
598 | keysymsdown[keysyms.lookup(0x43).keysym] = true; | |
599 | obj(elem); | |
600 | expect(elem).to.be.null; | |
601 | elem = {type: 'keyup', keyId: 0x41}; | |
602 | obj(elem); | |
603 | expect(times_called).to.be.equal(4); | |
604 | elem = {type: 'keyup', keyId: 0x42}; | |
605 | obj(elem); | |
606 | expect(times_called).to.be.equal(4); | |
607 | }); | |
608 | it('should add keysym as separate entry if keyId does not match last event and first is zero', function() { | |
609 | // this implies that separate keyups are required | |
610 | var times_called = 0; | |
611 | var elem = null; | |
612 | var keysymsdown = {}; | |
ae510306 | 613 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 614 | ++times_called; |
615 | if (elem.type == 'keyup') { | |
616 | expect(evt).to.have.property('keysym'); | |
617 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
618 | delete keysymsdown[evt.keysym.keysym]; | |
619 | } | |
620 | else { | |
621 | expect(evt).to.be.deep.equal(elem); | |
622 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
623 | elem = null; | |
624 | } | |
625 | }); | |
626 | ||
627 | expect(elem).to.be.null; | |
628 | elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)}; | |
629 | keysymsdown[keysyms.lookup(0x42).keysym] = true; | |
630 | obj(elem); | |
631 | expect(elem).to.be.null; | |
632 | elem = {type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x43)}; | |
633 | keysymsdown[keysyms.lookup(0x43).keysym] = true; | |
634 | obj(elem); | |
635 | expect(elem).to.be.null; | |
636 | expect(times_called).to.be.equal(2); | |
637 | elem = {type: 'keyup', keyId: 0}; | |
638 | obj(elem); | |
639 | expect(times_called).to.be.equal(3); | |
640 | elem = {type: 'keyup', keyId: 0x42}; | |
641 | obj(elem); | |
642 | expect(times_called).to.be.equal(4); | |
643 | }); | |
644 | it('should add keysym as separate entry if keyId does not match last event and second is zero', function() { | |
645 | // this implies that a separate keyups are required | |
646 | var times_called = 0; | |
647 | var elem = null; | |
648 | var keysymsdown = {}; | |
ae510306 | 649 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 650 | ++times_called; |
651 | if (elem.type == 'keyup') { | |
652 | expect(evt).to.have.property('keysym'); | |
653 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
654 | delete keysymsdown[evt.keysym.keysym]; | |
655 | } | |
656 | else { | |
657 | expect(evt).to.be.deep.equal(elem); | |
658 | expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined; | |
659 | elem = null; | |
660 | } | |
661 | }); | |
662 | ||
663 | expect(elem).to.be.null; | |
664 | elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}; | |
665 | keysymsdown[keysyms.lookup(0x42).keysym] = true; | |
666 | obj(elem); | |
667 | expect(elem).to.be.null; | |
668 | elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)}; | |
669 | keysymsdown[keysyms.lookup(0x43).keysym] = true; | |
670 | obj(elem); | |
671 | expect(elem).to.be.null; | |
672 | elem = {type: 'keyup', keyId: 0x41}; | |
673 | obj(elem); | |
674 | expect(times_called).to.be.equal(3); | |
675 | elem = {type: 'keyup', keyId: 0}; | |
676 | obj(elem); | |
677 | expect(times_called).to.be.equal(4); | |
678 | }); | |
679 | it('should pop matching key event on keyup', function() { | |
680 | var times_called = 0; | |
ae510306 | 681 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 682 | switch (times_called++) { |
683 | case 0: | |
684 | case 1: | |
685 | case 2: | |
686 | expect(evt.type).to.be.equal('keydown'); | |
687 | break; | |
688 | case 3: | |
689 | expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x42, keysym: keysyms.lookup(0x62)}); | |
690 | break; | |
691 | } | |
692 | }); | |
693 | ||
694 | obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)}); | |
695 | obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)}); | |
696 | obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)}); | |
697 | obj({type: 'keyup', keyId: 0x42}); | |
698 | expect(times_called).to.equal(4); | |
699 | }); | |
700 | it('should pop the first zero keyevent on keyup with zero keyId', function() { | |
701 | var times_called = 0; | |
ae510306 | 702 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 703 | switch (times_called++) { |
704 | case 0: | |
705 | case 1: | |
706 | case 2: | |
707 | expect(evt.type).to.be.equal('keydown'); | |
708 | break; | |
709 | case 3: | |
710 | expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x61)}); | |
711 | break; | |
712 | } | |
713 | }); | |
714 | ||
715 | obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x61)}); | |
716 | obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x62)}); | |
717 | obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x63)}); | |
718 | obj({type: 'keyup', keyId: 0x0}); | |
719 | expect(times_called).to.equal(4); | |
720 | }); | |
721 | it('should pop the last keyevents keysym if no match is found for keyId', function() { | |
722 | var times_called = 0; | |
ae510306 | 723 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 724 | switch (times_called++) { |
725 | case 0: | |
726 | case 1: | |
727 | case 2: | |
728 | expect(evt.type).to.be.equal('keydown'); | |
729 | break; | |
730 | case 3: | |
731 | expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x44, keysym: keysyms.lookup(0x63)}); | |
732 | break; | |
733 | } | |
734 | }); | |
735 | ||
736 | obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)}); | |
737 | obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)}); | |
738 | obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)}); | |
739 | obj({type: 'keyup', keyId: 0x44}); | |
740 | expect(times_called).to.equal(4); | |
741 | }); | |
742 | describe('Firefox sends keypress even when keydown is suppressed', function() { | |
743 | it('should discard the keypress', function() { | |
744 | var times_called = 0; | |
ae510306 | 745 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 746 | expect(times_called).to.be.equal(0); |
747 | ++times_called; | |
748 | }); | |
749 | ||
750 | obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
751 | expect(times_called).to.be.equal(1); | |
752 | obj({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)}); | |
753 | }); | |
754 | }); | |
755 | describe('releaseAll', function() { | |
756 | it('should do nothing if no keys have been pressed', function() { | |
757 | var times_called = 0; | |
ae510306 | 758 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 759 | ++times_called; |
760 | }); | |
761 | obj({type: 'releaseall'}); | |
762 | expect(times_called).to.be.equal(0); | |
763 | }); | |
764 | it('should release the keys that have been pressed', function() { | |
765 | var times_called = 0; | |
ae510306 | 766 | var obj = KeyboardUtil.TrackKeyState(function(evt) { |
f00b6fb6 | 767 | switch (times_called++) { |
768 | case 2: | |
769 | expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x41)}); | |
770 | break; | |
771 | case 3: | |
772 | expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x42)}); | |
773 | break; | |
774 | } | |
775 | }); | |
776 | obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)}); | |
777 | obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x42)}); | |
778 | expect(times_called).to.be.equal(2); | |
779 | obj({type: 'releaseall'}); | |
780 | expect(times_called).to.be.equal(4); | |
781 | obj({type: 'releaseall'}); | |
782 | expect(times_called).to.be.equal(4); | |
783 | }); | |
784 | }); | |
785 | ||
786 | }); | |
787 | ||
788 | describe('Escape Modifiers', function() { | |
789 | describe('Keydown', function() { | |
790 | it('should pass through when a char modifier is not down', function() { | |
791 | var times_called = 0; | |
ae510306 | 792 | KeyboardUtil.EscapeModifiers(function(evt) { |
f00b6fb6 | 793 | expect(times_called).to.be.equal(0); |
794 | ++times_called; | |
795 | expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
796 | })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
797 | expect(times_called).to.be.equal(1); | |
798 | }); | |
799 | it('should generate fake undo/redo events when a char modifier is down', function() { | |
800 | var times_called = 0; | |
ae510306 | 801 | KeyboardUtil.EscapeModifiers(function(evt) { |
f00b6fb6 | 802 | switch(times_called++) { |
803 | case 0: | |
804 | expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe9)}); | |
805 | break; | |
806 | case 1: | |
807 | expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe3)}); | |
808 | break; | |
809 | case 2: | |
810 | expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]}); | |
811 | break; | |
812 | case 3: | |
813 | expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe9)}); | |
814 | break; | |
815 | case 4: | |
816 | expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe3)}); | |
817 | break; | |
818 | } | |
819 | })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]}); | |
820 | expect(times_called).to.be.equal(5); | |
821 | }); | |
822 | }); | |
823 | describe('Keyup', function() { | |
824 | it('should pass through when a char modifier is down', function() { | |
825 | var times_called = 0; | |
ae510306 | 826 | KeyboardUtil.EscapeModifiers(function(evt) { |
f00b6fb6 | 827 | expect(times_called).to.be.equal(0); |
828 | ++times_called; | |
829 | expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]}); | |
830 | })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]}); | |
831 | expect(times_called).to.be.equal(1); | |
832 | }); | |
833 | it('should pass through when a char modifier is not down', function() { | |
834 | var times_called = 0; | |
ae510306 | 835 | KeyboardUtil.EscapeModifiers(function(evt) { |
f00b6fb6 | 836 | expect(times_called).to.be.equal(0); |
837 | ++times_called; | |
838 | expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
839 | })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)}); | |
840 | expect(times_called).to.be.equal(1); | |
841 | }); | |
842 | }); | |
843 | }); | |
844 | }); |