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