]> git.proxmox.com Git - mirror_novnc.git/blob - tests/test.keyboard.js
Remove modifier synchronisation
[mirror_novnc.git] / tests / test.keyboard.js
1 var assert = chai.assert;
2 var expect = chai.expect;
3
4 import keysyms from '../core/input/keysymdef.js';
5 import * as KeyboardUtil from '../core/input/util.js';
6
7 /* jshint newcap: false, expr: true */
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) {
12 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
13 expect(evt).to.be.an.object;
14 done();
15 }).keydown({code: 'KeyA', key: 'a'});
16 });
17 it('should pass the right keysym through', function(done) {
18 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
19 expect(evt.keysym).to.be.deep.equal(0x61);
20 done();
21 }).keypress({code: 'KeyA', key: 'a'});
22 });
23 it('should pass the right keyid through', function(done) {
24 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
25 expect(evt).to.have.property('code', 'KeyA');
26 done();
27 }).keydown({code: 'KeyA', key: 'a'});
28 });
29 it('should forward keydown events with the right type', function(done) {
30 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
31 expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'});
32 done();
33 }).keydown({code: 'KeyA', key: 'a'});
34 });
35 it('should forward keyup events with the right type', function(done) {
36 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
37 expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'});
38 done();
39 }).keyup({code: 'KeyA', key: 'a'});
40 });
41 it('should forward keypress events with the right type', function(done) {
42 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
43 expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'});
44 done();
45 }).keypress({code: 'KeyA', key: 'a'});
46 });
47 describe('suppress the right events at the right time', function() {
48 it('should suppress anything while a shortcut modifier is down', function() {
49 var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
50
51 obj.keydown({code: 'ControlLeft'});
52 expect(obj.keydown({code: 'KeyA', key: 'a'})).to.be.true;
53 expect(obj.keydown({code: 'Space', key: ' '})).to.be.true;
54 expect(obj.keydown({code: 'Digit1', key: '1'})).to.be.true;
55 expect(obj.keydown({code: 'IntlBackslash', key: '<'})).to.be.true;
56 expect(obj.keydown({code: 'Semicolon', key: 'ø'})).to.be.true;
57 });
58 it('should suppress non-character keys', function() {
59 var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
60
61 expect(obj.keydown({code: 'Backspace'}), 'a').to.be.true;
62 expect(obj.keydown({code: 'Tab'}), 'b').to.be.true;
63 expect(obj.keydown({code: 'ControlLeft'}), 'd').to.be.true;
64 expect(obj.keydown({code: 'AltLeft'}), 'e').to.be.true;
65 });
66 it('should generate event for shift keydown', function() {
67 var called = false;
68 var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
69 expect(evt).to.have.property('keysym');
70 called = true;
71 }).keydown({code: 'ShiftLeft'});
72 expect(called).to.be.true;
73 });
74 it('should suppress character keys with key', function() {
75 var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
76
77 expect(obj.keydown({code: 'KeyA', key: 'a'})).to.be.true;
78 expect(obj.keydown({code: 'Digit1', key: '1'})).to.be.true;
79 expect(obj.keydown({code: 'IntlBackslash', key: '<'})).to.be.true;
80 expect(obj.keydown({code: 'Semicolon', key: 'ø'})).to.be.true;
81 });
82 it('should not suppress character keys without key', function() {
83 var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
84
85 expect(obj.keydown({code: 'KeyA'})).to.be.false;
86 expect(obj.keydown({code: 'Digit1'})).to.be.false;
87 expect(obj.keydown({code: 'IntlBackslash'})).to.be.false;
88 expect(obj.keydown({code: 'Semicolon'})).to.be.false;
89 });
90 });
91 describe('Keypress and keyup events', function() {
92 it('should always suppress event propagation', function() {
93 var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
94
95 expect(obj.keypress({code: 'KeyA', key: 'a'})).to.be.true;
96 expect(obj.keypress({code: 'IntlBackslash', key: '<'})).to.be.true;
97 expect(obj.keypress({code: 'ControlLeft', key: 'Control'})).to.be.true;
98
99 expect(obj.keyup({code: 'KeyA', key: 'a'})).to.be.true;
100 expect(obj.keyup({code: 'IntlBackslash', key: '<'})).to.be.true;
101 expect(obj.keyup({code: 'ControlLeft', key: 'Control'})).to.be.true;
102 });
103 });
104 describe('mark events if a char modifier is down', function() {
105 it('should not mark modifiers on a keydown event', function() {
106 var times_called = 0;
107 var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
108 switch (times_called++) {
109 case 0: //altgr
110 break;
111 case 1: // 'a'
112 expect(evt).to.not.have.property('escape');
113 break;
114 }
115 });
116
117 obj.keydown({code: 'AltRight', key: 'AltGraph'})
118 obj.keydown({code: 'KeyA', key: 'a'});
119 });
120
121 it('should indicate on events if a single-key char modifier is down', function(done) {
122 var times_called = 0;
123 var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
124 switch (times_called++) {
125 case 0: //altgr
126 break;
127 case 1: // 'a'
128 expect(evt).to.be.deep.equal({
129 type: 'keypress',
130 code: 'KeyA',
131 keysym: 0x61,
132 escape: [0xfe03]
133 });
134 done();
135 return;
136 }
137 });
138
139 obj.keydown({code: 'AltRight', key: 'AltGraph'})
140 obj.keypress({code: 'KeyA', key: 'a'});
141 });
142 it('should indicate on events if a multi-key char modifier is down', function(done) {
143 var times_called = 0;
144 var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xffe9, 0xffe3]), function(evt) {
145 switch (times_called++) {
146 case 0: //ctrl
147 break;
148 case 1: //alt
149 break;
150 case 2: // 'a'
151 expect(evt).to.be.deep.equal({
152 type: 'keypress',
153 code: 'KeyA',
154 keysym: 0x61,
155 escape: [0xffe9, 0xffe3]
156 });
157 done();
158 return;
159 }
160 });
161
162 obj.keydown({code: 'ControlLeft'});
163 obj.keydown({code: 'AltLeft'});
164 obj.keypress({code: 'KeyA', key: 'a'});
165 });
166 it('should not consider a char modifier to be down on the modifier key itself', function() {
167 var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
168 expect(evt).to.not.have.property('escape');
169 });
170
171 obj.keydown({code: 'AltRight', key: 'AltGraph'})
172
173 });
174 });
175 });
176
177 describe('Track Key State', function() {
178 it('should do nothing on keyup events if no keys are down', function() {
179 var obj = KeyboardUtil.TrackKeyState(function(evt) {
180 expect(true).to.be.false;
181 });
182 obj({type: 'keyup', code: 'KeyA'});
183 });
184 it('should insert into the queue on keydown if no keys are down', function() {
185 var times_called = 0;
186 var elem = null;
187 var keysymsdown = {};
188 var obj = KeyboardUtil.TrackKeyState(function(evt) {
189 ++times_called;
190 if (elem.type == 'keyup') {
191 expect(evt).to.have.property('keysym');
192 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
193 delete keysymsdown[evt.keysym];
194 }
195 else {
196 expect(evt).to.be.deep.equal(elem);
197 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
198 }
199 elem = null;
200 });
201
202 expect(elem).to.be.null;
203 elem = {type: 'keydown', code: 'KeyA', keysym: 0x42};
204 keysymsdown[0x42] = true;
205 obj(elem);
206 expect(elem).to.be.null;
207 elem = {type: 'keyup', code: 'KeyA'};
208 obj(elem);
209 expect(elem).to.be.null;
210 expect(times_called).to.be.equal(2);
211 });
212 it('should insert into the queue on keypress if no keys are down', function() {
213 var times_called = 0;
214 var elem = null;
215 var keysymsdown = {};
216 var obj = KeyboardUtil.TrackKeyState(function(evt) {
217 ++times_called;
218 if (elem.type == 'keyup') {
219 expect(evt).to.have.property('keysym');
220 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
221 delete keysymsdown[evt.keysym];
222 }
223 else {
224 expect(evt).to.be.deep.equal(elem);
225 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
226 }
227 elem = null;
228 });
229
230 expect(elem).to.be.null;
231 elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
232 keysymsdown[0x42] = true;
233 obj(elem);
234 expect(elem).to.be.null;
235 elem = {type: 'keyup', code: 'KeyA'};
236 obj(elem);
237 expect(elem).to.be.null;
238 expect(times_called).to.be.equal(2);
239 });
240 it('should add keysym to last key entry if code matches', function() {
241 // this implies that a single keyup will release both keysyms
242 var times_called = 0;
243 var elem = null;
244 var keysymsdown = {};
245 var obj = KeyboardUtil.TrackKeyState(function(evt) {
246 ++times_called;
247 if (elem.type == 'keyup') {
248 expect(evt).to.have.property('keysym');
249 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
250 delete keysymsdown[evt.keysym];
251 }
252 else {
253 expect(evt).to.be.deep.equal(elem);
254 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
255 elem = null;
256 }
257 });
258
259 expect(elem).to.be.null;
260 elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
261 keysymsdown[0x42] = true;
262 obj(elem);
263 expect(elem).to.be.null;
264 elem = {type: 'keypress', code: 'KeyA', keysym: 0x43};
265 keysymsdown[0x43] = true;
266 obj(elem);
267 expect(elem).to.be.null;
268 elem = {type: 'keyup', code: 'KeyA'};
269 obj(elem);
270 expect(times_called).to.be.equal(4);
271 });
272 it('should create new key entry if code matches and keysym does not', function() {
273 // this implies that a single keyup will release both keysyms
274 var times_called = 0;
275 var elem = null;
276 var keysymsdown = {};
277 var obj = KeyboardUtil.TrackKeyState(function(evt) {
278 ++times_called;
279 if (elem.type == 'keyup') {
280 expect(evt).to.have.property('keysym');
281 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
282 delete keysymsdown[evt.keysym];
283 }
284 else {
285 expect(evt).to.be.deep.equal(elem);
286 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
287 elem = null;
288 }
289 });
290
291 expect(elem).to.be.null;
292 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
293 keysymsdown[0x42] = true;
294 obj(elem);
295 expect(elem).to.be.null;
296 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x43};
297 keysymsdown[0x43] = true;
298 obj(elem);
299 expect(times_called).to.be.equal(2);
300 expect(elem).to.be.null;
301 elem = {type: 'keyup', code: 'Unidentified'};
302 obj(elem);
303 expect(times_called).to.be.equal(3);
304 elem = {type: 'keyup', code: 'Unidentified'};
305 obj(elem);
306 expect(times_called).to.be.equal(4);
307 });
308 it('should merge key entry if codes are zero and keysyms match', function() {
309 // this implies that a single keyup will release both keysyms
310 var times_called = 0;
311 var elem = null;
312 var keysymsdown = {};
313 var obj = KeyboardUtil.TrackKeyState(function(evt) {
314 ++times_called;
315 if (elem.type == 'keyup') {
316 expect(evt).to.have.property('keysym');
317 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
318 delete keysymsdown[evt.keysym];
319 }
320 else {
321 expect(evt).to.be.deep.equal(elem);
322 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
323 elem = null;
324 }
325 });
326
327 expect(elem).to.be.null;
328 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
329 keysymsdown[0x42] = true;
330 obj(elem);
331 expect(elem).to.be.null;
332 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
333 keysymsdown[0x42] = true;
334 obj(elem);
335 expect(times_called).to.be.equal(2);
336 expect(elem).to.be.null;
337 elem = {type: 'keyup', code: 'Unidentified'};
338 obj(elem);
339 expect(times_called).to.be.equal(3);
340 });
341 it('should add keysym as separate entry if code does not match last event', function() {
342 // this implies that separate keyups are required
343 var times_called = 0;
344 var elem = null;
345 var keysymsdown = {};
346 var obj = KeyboardUtil.TrackKeyState(function(evt) {
347 ++times_called;
348 if (elem.type == 'keyup') {
349 expect(evt).to.have.property('keysym');
350 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
351 delete keysymsdown[evt.keysym];
352 }
353 else {
354 expect(evt).to.be.deep.equal(elem);
355 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
356 elem = null;
357 }
358 });
359
360 expect(elem).to.be.null;
361 elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
362 keysymsdown[0x42] = true;
363 obj(elem);
364 expect(elem).to.be.null;
365 elem = {type: 'keypress', code: 'KeyB', keysym: 0x43};
366 keysymsdown[0x43] = true;
367 obj(elem);
368 expect(elem).to.be.null;
369 elem = {type: 'keyup', code: 'KeyA'};
370 obj(elem);
371 expect(times_called).to.be.equal(4);
372 elem = {type: 'keyup', code: 'KeyB'};
373 obj(elem);
374 expect(times_called).to.be.equal(4);
375 });
376 it('should add keysym as separate entry if code does not match last event and first is zero', function() {
377 // this implies that separate keyups are required
378 var times_called = 0;
379 var elem = null;
380 var keysymsdown = {};
381 var obj = KeyboardUtil.TrackKeyState(function(evt) {
382 ++times_called;
383 if (elem.type == 'keyup') {
384 expect(evt).to.have.property('keysym');
385 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
386 delete keysymsdown[evt.keysym];
387 }
388 else {
389 expect(evt).to.be.deep.equal(elem);
390 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
391 elem = null;
392 }
393 });
394
395 expect(elem).to.be.null;
396 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
397 keysymsdown[0x42] = true;
398 obj(elem);
399 expect(elem).to.be.null;
400 elem = {type: 'keydown', code: 'KeyB', keysym: 0x43};
401 keysymsdown[0x43] = true;
402 obj(elem);
403 expect(elem).to.be.null;
404 expect(times_called).to.be.equal(2);
405 elem = {type: 'keyup', code: 'Unidentified'};
406 obj(elem);
407 expect(times_called).to.be.equal(3);
408 elem = {type: 'keyup', code: 'KeyB'};
409 obj(elem);
410 expect(times_called).to.be.equal(4);
411 });
412 it('should add keysym as separate entry if code does not match last event and second is zero', function() {
413 // this implies that a separate keyups are required
414 var times_called = 0;
415 var elem = null;
416 var keysymsdown = {};
417 var obj = KeyboardUtil.TrackKeyState(function(evt) {
418 ++times_called;
419 if (elem.type == 'keyup') {
420 expect(evt).to.have.property('keysym');
421 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
422 delete keysymsdown[evt.keysym];
423 }
424 else {
425 expect(evt).to.be.deep.equal(elem);
426 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
427 elem = null;
428 }
429 });
430
431 expect(elem).to.be.null;
432 elem = {type: 'keydown', code: 'KeyA', keysym: 0x42};
433 keysymsdown[0x42] = true;
434 obj(elem);
435 expect(elem).to.be.null;
436 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x43};
437 keysymsdown[0x43] = true;
438 obj(elem);
439 expect(elem).to.be.null;
440 elem = {type: 'keyup', code: 'KeyA'};
441 obj(elem);
442 expect(times_called).to.be.equal(3);
443 elem = {type: 'keyup', code: 'Unidentified'};
444 obj(elem);
445 expect(times_called).to.be.equal(4);
446 });
447 it('should pop matching key event on keyup', function() {
448 var times_called = 0;
449 var obj = KeyboardUtil.TrackKeyState(function(evt) {
450 switch (times_called++) {
451 case 0:
452 case 1:
453 case 2:
454 expect(evt.type).to.be.equal('keydown');
455 break;
456 case 3:
457 expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyB', keysym: 0x62});
458 break;
459 }
460 });
461
462 obj({type: 'keydown', code: 'KeyA', keysym: 0x61});
463 obj({type: 'keydown', code: 'KeyB', keysym: 0x62});
464 obj({type: 'keydown', code: 'KeyC', keysym: 0x63});
465 obj({type: 'keyup', code: 'KeyB'});
466 expect(times_called).to.equal(4);
467 });
468 it('should pop the first zero keyevent on keyup with zero code', function() {
469 var times_called = 0;
470 var obj = KeyboardUtil.TrackKeyState(function(evt) {
471 switch (times_called++) {
472 case 0:
473 case 1:
474 case 2:
475 expect(evt.type).to.be.equal('keydown');
476 break;
477 case 3:
478 expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x61});
479 break;
480 }
481 });
482
483 obj({type: 'keydown', code: 'Unidentified', keysym: 0x61});
484 obj({type: 'keydown', code: 'Unidentified', keysym: 0x62});
485 obj({type: 'keydown', code: 'KeyA', keysym: 0x63});
486 obj({type: 'keyup', code: 'Unidentified'});
487 expect(times_called).to.equal(4);
488 });
489 it('should pop the last keyevents keysym if no match is found for code', function() {
490 var times_called = 0;
491 var obj = KeyboardUtil.TrackKeyState(function(evt) {
492 switch (times_called++) {
493 case 0:
494 case 1:
495 case 2:
496 expect(evt.type).to.be.equal('keydown');
497 break;
498 case 3:
499 expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyD', keysym: 0x63});
500 break;
501 }
502 });
503
504 obj({type: 'keydown', code: 'KeyA', keysym: 0x61});
505 obj({type: 'keydown', code: 'KeyB', keysym: 0x62});
506 obj({type: 'keydown', code: 'KeyC', keysym: 0x63});
507 obj({type: 'keyup', code: 'KeyD'});
508 expect(times_called).to.equal(4);
509 });
510 describe('Firefox sends keypress even when keydown is suppressed', function() {
511 it('should discard the keypress', function() {
512 var times_called = 0;
513 var obj = KeyboardUtil.TrackKeyState(function(evt) {
514 expect(times_called).to.be.equal(0);
515 ++times_called;
516 });
517
518 obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
519 expect(times_called).to.be.equal(1);
520 obj({type: 'keypress', code: 'KeyA', keysym: 0x43});
521 });
522 });
523 describe('releaseAll', function() {
524 it('should do nothing if no keys have been pressed', function() {
525 var times_called = 0;
526 var obj = KeyboardUtil.TrackKeyState(function(evt) {
527 ++times_called;
528 });
529 obj({type: 'releaseall'});
530 expect(times_called).to.be.equal(0);
531 });
532 it('should release the keys that have been pressed', function() {
533 var times_called = 0;
534 var obj = KeyboardUtil.TrackKeyState(function(evt) {
535 switch (times_called++) {
536 case 2:
537 expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x41});
538 break;
539 case 3:
540 expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x42});
541 break;
542 }
543 });
544 obj({type: 'keydown', code: 'KeyA', keysym: 0x41});
545 obj({type: 'keydown', code: 'KeyB', keysym: 0x42});
546 expect(times_called).to.be.equal(2);
547 obj({type: 'releaseall'});
548 expect(times_called).to.be.equal(4);
549 obj({type: 'releaseall'});
550 expect(times_called).to.be.equal(4);
551 });
552 });
553
554 });
555
556 describe('Escape Modifiers', function() {
557 describe('Keydown', function() {
558 it('should pass through when a char modifier is not down', function() {
559 var times_called = 0;
560 KeyboardUtil.EscapeModifiers(function(evt) {
561 expect(times_called).to.be.equal(0);
562 ++times_called;
563 expect(evt).to.be.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42});
564 })({type: 'keydown', code: 'KeyA', keysym: 0x42});
565 expect(times_called).to.be.equal(1);
566 });
567 it('should generate fake undo/redo events when a char modifier is down', function() {
568 var times_called = 0;
569 KeyboardUtil.EscapeModifiers(function(evt) {
570 switch(times_called++) {
571 case 0:
572 expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe9});
573 break;
574 case 1:
575 expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe3});
576 break;
577 case 2:
578 expect(evt).to.be.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42, escape: [0xffe9, 0xffe3]});
579 break;
580 case 3:
581 expect(evt).to.be.deep.equal({type: 'keydown', code: 'Unidentified', keysym: 0xffe9});
582 break;
583 case 4:
584 expect(evt).to.be.deep.equal({type: 'keydown', code: 'Unidentified', keysym: 0xffe3});
585 break;
586 }
587 })({type: 'keydown', code: 'KeyA', keysym: 0x42, escape: [0xffe9, 0xffe3]});
588 expect(times_called).to.be.equal(5);
589 });
590 });
591 describe('Keyup', function() {
592 it('should pass through when a char modifier is down', function() {
593 var times_called = 0;
594 KeyboardUtil.EscapeModifiers(function(evt) {
595 expect(times_called).to.be.equal(0);
596 ++times_called;
597 expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]});
598 })({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]});
599 expect(times_called).to.be.equal(1);
600 });
601 it('should pass through when a char modifier is not down', function() {
602 var times_called = 0;
603 KeyboardUtil.EscapeModifiers(function(evt) {
604 expect(times_called).to.be.equal(0);
605 ++times_called;
606 expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42});
607 })({type: 'keyup', code: 'KeyA', keysym: 0x42});
608 expect(times_called).to.be.equal(1);
609 });
610 });
611 });
612 });