]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.keyboard.js
Use standard DOM identifiers for physical keys
[mirror_novnc.git] / tests / test.keyboard.js
CommitLineData
f00b6fb6 1var assert = chai.assert;
2var expect = chai.expect;
3
dfae3209
SR
4import keysyms from '../core/input/keysymdef.js';
5import * as KeyboardUtil from '../core/input/util.js';
6
31f169e8 7/* jshint newcap: false, expr: true */
f00b6fb6 8describe('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();
80cb8ffd 15 }).keydown({code: 'KeyA', keyCode: 0x41});
f00b6fb6 16 });
17 it('should pass the right keysym through', function(done) {
ae510306 18 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
524d67f2 19 expect(evt.keysym).to.be.deep.equal(0x61);
f00b6fb6 20 done();
80cb8ffd 21 }).keypress({code: 'KeyA', keyCode: 0x41});
f00b6fb6 22 });
23 it('should pass the right keyid through', function(done) {
ae510306 24 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
80cb8ffd 25 expect(evt).to.have.property('code', 'KeyA');
f00b6fb6 26 done();
80cb8ffd 27 }).keydown({code: 'KeyA', keyCode: 0x41});
f00b6fb6 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;
80cb8ffd 34 }).keypress({code: 'KeyA', keyCode: 0x41, ctrlKey: true});
f00b6fb6 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
524d67f2 42 expect(evt).to.be.deep.equal({keysym: 0xffe3, type: 'keydown'});
f00b6fb6 43 ++count;
44 break;
45 case 1:
80cb8ffd 46 expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown', keysym: 0x61});
f00b6fb6 47 done();
48 break;
49 }
80cb8ffd 50 }).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true});
f00b6fb6 51 });
52 it('should forward keydown events with the right type', function(done) {
ae510306 53 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
80cb8ffd 54 expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'});
f00b6fb6 55 done();
80cb8ffd 56 }).keydown({code: 'KeyA', keyCode: 0x41});
f00b6fb6 57 });
58 it('should forward keyup events with the right type', function(done) {
ae510306 59 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
80cb8ffd 60 expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'});
f00b6fb6 61 done();
80cb8ffd 62 }).keyup({code: 'KeyA', keyCode: 0x41});
f00b6fb6 63 });
64 it('should forward keypress events with the right type', function(done) {
ae510306 65 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
80cb8ffd 66 expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'});
f00b6fb6 67 done();
80cb8ffd 68 }).keypress({code: 'KeyA', keyCode: 0x41});
f00b6fb6 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
524d67f2 75 expect(evt).to.be.deep.equal({keysym: 0xfe03, type: 'keydown'});
f00b6fb6 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',
80cb8ffd 85 code: 'KeyA',
524d67f2 86 keysym: 0x61
f00b6fb6 87 });
88
89 done();
90 break;
91 }
80cb8ffd 92 }).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true});
f00b6fb6 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
80cb8ffd 187 obj.keydown({code: 'KeyA', keyCode: 0x41});
f00b6fb6 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',
80cb8ffd
PO
199 code: 'KeyA',
200 keysym: 0x61,
f00b6fb6 201 escape: [0xfe03]
202 });
203 done();
204 return;
205 }
206 });
207
208 obj.keydown({keyCode: 0xe1}); // press altgr
80cb8ffd 209 obj.keypress({code: 'KeyA', keyCode: 0x41});
f00b6fb6 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',
80cb8ffd
PO
222 code: 'KeyA',
223 keysym: 0x61,
f00b6fb6 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
80cb8ffd 233 obj.keypress({code: 'KeyA', keyCode: 0x41});
f00b6fb6 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) {
80cb8ffd
PO
247 expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'});
248 }).keydown({code: 'KeyA', keyCode: 0x41});
f00b6fb6 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:
80cb8ffd 255 expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'});
f00b6fb6 256 break;
257 }
80cb8ffd 258 }).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true});
f00b6fb6 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:
80cb8ffd 266 expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'});
f00b6fb6 267 break;
268 }
80cb8ffd 269 }).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true});
f00b6fb6 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) {
80cb8ffd 274 expect(evt, 'tab').to.be.deep.equal({code: 'Tab', keysym: 0xff09, type: 'keydown'});
f00b6fb6 275 }).keydown({keyCode: 0x09});
276
ae510306 277 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
80cb8ffd 278 expect(evt, 'ctrl').to.be.deep.equal({code: 'ControlLeft', keysym: 0xffe3, type: 'keydown'});
f00b6fb6 279 }).keydown({keyCode: 0x11});
280 });
281 it('should never remove keysym from keypress', function() {
ae510306 282 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
80cb8ffd
PO
283 expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'});
284 }).keypress({code: 'KeyA', keyCode: 0x41});
f00b6fb6 285 });
286 it('should never remove keysym from keyup', function() {
ae510306 287 KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
80cb8ffd
PO
288 expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'});
289 }).keyup({code: 'KeyA', keyCode: 0x41});
f00b6fb6 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){
80cb8ffd 299 expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x41});
f00b6fb6 300 done();
80cb8ffd 301 })({type: 'keydown', code: 'KeyA', keysym: 0x41});
f00b6fb6 302 });
303 it('should pass keyup events through if there is no stall', function(done) {
ae510306 304 var obj = KeyboardUtil.VerifyCharModifier(function(evt){
80cb8ffd 305 expect(evt).to.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x41});
f00b6fb6 306 done();
80cb8ffd 307 })({type: 'keyup', code: 'KeyA', keysym: 0x41});
f00b6fb6 308 });
309 it('should pass keypress events through if there is no stall', function(done) {
ae510306 310 var obj = KeyboardUtil.VerifyCharModifier(function(evt){
80cb8ffd 311 expect(evt).to.deep.equal({type: 'keypress', code: 'KeyA', keysym: 0x41});
f00b6fb6 312 done();
80cb8ffd 313 })({type: 'keypress', code: 'KeyA', keysym: 0x41});
f00b6fb6 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
80cb8ffd 318 expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x41});
f00b6fb6 319 done();
320 });
321
322 obj({type: 'stall'});
80cb8ffd 323 obj({type: 'keydown', code: 'KeyA', keysym: 0x41});
f00b6fb6 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;
80cb8ffd 331 expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44});
f00b6fb6 332 done();
333 });
334
335 obj({type: 'stall'});
80cb8ffd
PO
336 obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
337 obj({type: 'keypress', code: 'KeyC', keysym: 0x44});
f00b6fb6 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;
80cb8ffd 346 expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44, escape: [0xffe3]});
f00b6fb6 347 done();
348 });
349
350 obj({type: 'stall'});
80cb8ffd
PO
351 obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
352 obj({type: 'keypress', code: 'KeyC', keysym: 0x44, escape: [0xffe3]});
f00b6fb6 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'});
80cb8ffd
PO
361 obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
362 obj({type: 'keypress', code: 'KeyC', keysym: 0x42, escape: [0xffe3]});
f00b6fb6 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:
80cb8ffd 369 expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42});
f00b6fb6 370 break;
371 case 1:
80cb8ffd 372 expect(evt).to.deep.equal({type: 'keypress', code: 'KeyC', keysym: 0x44});
f00b6fb6 373 done();
374 break;
375 }
376
377 ++times_called;
378 });
379
80cb8ffd
PO
380 obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
381 obj({type: 'keypress', code: 'KeyC', keysym: 0x44});
f00b6fb6 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:
80cb8ffd 388 expect(evt,1).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42});
f00b6fb6 389 break;
390 case 1:
80cb8ffd 391 expect(evt,2).to.deep.equal({type: 'keyup', code: 'KeyC', keysym: 0x44});
f00b6fb6 392 break;
393 case 2:
80cb8ffd 394 expect(evt,3).to.deep.equal({type: 'keypress', code: 'KeyE', keysym: 0x46});
f00b6fb6 395 done();
396 break;
397 }
398
399 ++times_called;
400 });
401
402 obj({type: 'stall'});
80cb8ffd
PO
403 obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
404 obj({type: 'keyup', code: 'KeyC', keysym: 0x44});
405 obj({type: 'keypress', code: 'KeyE', keysym: 0x46});
f00b6fb6 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 });
80cb8ffd 414 obj({type: 'keyup', code: 'KeyA'});
f00b6fb6 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');
524d67f2
PO
424 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
425 delete keysymsdown[evt.keysym];
f00b6fb6 426 }
427 else {
428 expect(evt).to.be.deep.equal(elem);
524d67f2 429 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
f00b6fb6 430 }
431 elem = null;
432 });
433
434 expect(elem).to.be.null;
80cb8ffd 435 elem = {type: 'keydown', code: 'KeyA', keysym: 0x42};
524d67f2 436 keysymsdown[0x42] = true;
f00b6fb6 437 obj(elem);
438 expect(elem).to.be.null;
80cb8ffd 439 elem = {type: 'keyup', code: 'KeyA'};
f00b6fb6 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');
524d67f2
PO
452 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
453 delete keysymsdown[evt.keysym];
f00b6fb6 454 }
455 else {
456 expect(evt).to.be.deep.equal(elem);
524d67f2 457 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
f00b6fb6 458 }
459 elem = null;
460 });
461
462 expect(elem).to.be.null;
80cb8ffd 463 elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
524d67f2 464 keysymsdown[0x42] = true;
f00b6fb6 465 obj(elem);
466 expect(elem).to.be.null;
80cb8ffd 467 elem = {type: 'keyup', code: 'KeyA'};
f00b6fb6 468 obj(elem);
469 expect(elem).to.be.null;
470 expect(times_called).to.be.equal(2);
471 });
80cb8ffd 472 it('should add keysym to last key entry if code matches', function() {
f00b6fb6 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');
524d67f2
PO
481 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
482 delete keysymsdown[evt.keysym];
f00b6fb6 483 }
484 else {
485 expect(evt).to.be.deep.equal(elem);
524d67f2 486 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
f00b6fb6 487 elem = null;
488 }
489 });
490
491 expect(elem).to.be.null;
80cb8ffd 492 elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
524d67f2 493 keysymsdown[0x42] = true;
f00b6fb6 494 obj(elem);
495 expect(elem).to.be.null;
80cb8ffd 496 elem = {type: 'keypress', code: 'KeyA', keysym: 0x43};
524d67f2 497 keysymsdown[0x43] = true;
f00b6fb6 498 obj(elem);
499 expect(elem).to.be.null;
80cb8ffd 500 elem = {type: 'keyup', code: 'KeyA'};
f00b6fb6 501 obj(elem);
502 expect(times_called).to.be.equal(4);
503 });
80cb8ffd 504 it('should create new key entry if code matches and keysym does not', function() {
f00b6fb6 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');
524d67f2
PO
513 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
514 delete keysymsdown[evt.keysym];
f00b6fb6 515 }
516 else {
517 expect(evt).to.be.deep.equal(elem);
524d67f2 518 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
f00b6fb6 519 elem = null;
520 }
521 });
522
523 expect(elem).to.be.null;
80cb8ffd 524 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
524d67f2 525 keysymsdown[0x42] = true;
f00b6fb6 526 obj(elem);
527 expect(elem).to.be.null;
80cb8ffd 528 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x43};
524d67f2 529 keysymsdown[0x43] = true;
f00b6fb6 530 obj(elem);
531 expect(times_called).to.be.equal(2);
532 expect(elem).to.be.null;
80cb8ffd 533 elem = {type: 'keyup', code: 'Unidentified'};
f00b6fb6 534 obj(elem);
535 expect(times_called).to.be.equal(3);
80cb8ffd 536 elem = {type: 'keyup', code: 'Unidentified'};
f00b6fb6 537 obj(elem);
538 expect(times_called).to.be.equal(4);
539 });
80cb8ffd 540 it('should merge key entry if codes are zero and keysyms match', function() {
f00b6fb6 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');
524d67f2
PO
549 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
550 delete keysymsdown[evt.keysym];
f00b6fb6 551 }
552 else {
553 expect(evt).to.be.deep.equal(elem);
524d67f2 554 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
f00b6fb6 555 elem = null;
556 }
557 });
558
559 expect(elem).to.be.null;
80cb8ffd 560 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
524d67f2 561 keysymsdown[0x42] = true;
f00b6fb6 562 obj(elem);
563 expect(elem).to.be.null;
80cb8ffd 564 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
524d67f2 565 keysymsdown[0x42] = true;
f00b6fb6 566 obj(elem);
567 expect(times_called).to.be.equal(2);
568 expect(elem).to.be.null;
80cb8ffd 569 elem = {type: 'keyup', code: 'Unidentified'};
f00b6fb6 570 obj(elem);
571 expect(times_called).to.be.equal(3);
572 });
80cb8ffd 573 it('should add keysym as separate entry if code does not match last event', function() {
f00b6fb6 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');
524d67f2
PO
582 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
583 delete keysymsdown[evt.keysym];
f00b6fb6 584 }
585 else {
586 expect(evt).to.be.deep.equal(elem);
524d67f2 587 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
f00b6fb6 588 elem = null;
589 }
590 });
591
592 expect(elem).to.be.null;
80cb8ffd 593 elem = {type: 'keypress', code: 'KeyA', keysym: 0x42};
524d67f2 594 keysymsdown[0x42] = true;
f00b6fb6 595 obj(elem);
596 expect(elem).to.be.null;
80cb8ffd 597 elem = {type: 'keypress', code: 'KeyB', keysym: 0x43};
524d67f2 598 keysymsdown[0x43] = true;
f00b6fb6 599 obj(elem);
600 expect(elem).to.be.null;
80cb8ffd 601 elem = {type: 'keyup', code: 'KeyA'};
f00b6fb6 602 obj(elem);
603 expect(times_called).to.be.equal(4);
80cb8ffd 604 elem = {type: 'keyup', code: 'KeyB'};
f00b6fb6 605 obj(elem);
606 expect(times_called).to.be.equal(4);
607 });
80cb8ffd 608 it('should add keysym as separate entry if code does not match last event and first is zero', function() {
f00b6fb6 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');
524d67f2
PO
617 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
618 delete keysymsdown[evt.keysym];
f00b6fb6 619 }
620 else {
621 expect(evt).to.be.deep.equal(elem);
524d67f2 622 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
f00b6fb6 623 elem = null;
624 }
625 });
626
627 expect(elem).to.be.null;
80cb8ffd 628 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x42};
524d67f2 629 keysymsdown[0x42] = true;
f00b6fb6 630 obj(elem);
631 expect(elem).to.be.null;
80cb8ffd 632 elem = {type: 'keydown', code: 'KeyB', keysym: 0x43};
524d67f2 633 keysymsdown[0x43] = true;
f00b6fb6 634 obj(elem);
635 expect(elem).to.be.null;
636 expect(times_called).to.be.equal(2);
80cb8ffd 637 elem = {type: 'keyup', code: 'Unidentified'};
f00b6fb6 638 obj(elem);
639 expect(times_called).to.be.equal(3);
80cb8ffd 640 elem = {type: 'keyup', code: 'KeyB'};
f00b6fb6 641 obj(elem);
642 expect(times_called).to.be.equal(4);
643 });
80cb8ffd 644 it('should add keysym as separate entry if code does not match last event and second is zero', function() {
f00b6fb6 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');
524d67f2
PO
653 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
654 delete keysymsdown[evt.keysym];
f00b6fb6 655 }
656 else {
657 expect(evt).to.be.deep.equal(elem);
524d67f2 658 expect (keysymsdown[evt.keysym]).to.not.be.undefined;
f00b6fb6 659 elem = null;
660 }
661 });
662
663 expect(elem).to.be.null;
80cb8ffd 664 elem = {type: 'keydown', code: 'KeyA', keysym: 0x42};
524d67f2 665 keysymsdown[0x42] = true;
f00b6fb6 666 obj(elem);
667 expect(elem).to.be.null;
80cb8ffd 668 elem = {type: 'keydown', code: 'Unidentified', keysym: 0x43};
524d67f2 669 keysymsdown[0x43] = true;
f00b6fb6 670 obj(elem);
671 expect(elem).to.be.null;
80cb8ffd 672 elem = {type: 'keyup', code: 'KeyA'};
f00b6fb6 673 obj(elem);
674 expect(times_called).to.be.equal(3);
80cb8ffd 675 elem = {type: 'keyup', code: 'Unidentified'};
f00b6fb6 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:
80cb8ffd 689 expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyB', keysym: 0x62});
f00b6fb6 690 break;
691 }
692 });
693
80cb8ffd
PO
694 obj({type: 'keydown', code: 'KeyA', keysym: 0x61});
695 obj({type: 'keydown', code: 'KeyB', keysym: 0x62});
696 obj({type: 'keydown', code: 'KeyC', keysym: 0x63});
697 obj({type: 'keyup', code: 'KeyB'});
f00b6fb6 698 expect(times_called).to.equal(4);
699 });
80cb8ffd 700 it('should pop the first zero keyevent on keyup with zero code', function() {
f00b6fb6 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:
80cb8ffd 710 expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x61});
f00b6fb6 711 break;
712 }
713 });
714
80cb8ffd
PO
715 obj({type: 'keydown', code: 'Unidentified', keysym: 0x61});
716 obj({type: 'keydown', code: 'Unidentified', keysym: 0x62});
717 obj({type: 'keydown', code: 'KeyA', keysym: 0x63});
718 obj({type: 'keyup', code: 'Unidentified'});
f00b6fb6 719 expect(times_called).to.equal(4);
720 });
80cb8ffd 721 it('should pop the last keyevents keysym if no match is found for code', function() {
f00b6fb6 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:
80cb8ffd 731 expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyD', keysym: 0x63});
f00b6fb6 732 break;
733 }
734 });
735
80cb8ffd
PO
736 obj({type: 'keydown', code: 'KeyA', keysym: 0x61});
737 obj({type: 'keydown', code: 'KeyB', keysym: 0x62});
738 obj({type: 'keydown', code: 'KeyC', keysym: 0x63});
739 obj({type: 'keyup', code: 'KeyD'});
f00b6fb6 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
80cb8ffd 750 obj({type: 'keydown', code: 'KeyA', keysym: 0x42});
f00b6fb6 751 expect(times_called).to.be.equal(1);
80cb8ffd 752 obj({type: 'keypress', code: 'KeyA', keysym: 0x43});
f00b6fb6 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:
80cb8ffd 769 expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x41});
f00b6fb6 770 break;
771 case 3:
80cb8ffd 772 expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0x42});
f00b6fb6 773 break;
774 }
775 });
80cb8ffd
PO
776 obj({type: 'keydown', code: 'KeyA', keysym: 0x41});
777 obj({type: 'keydown', code: 'KeyB', keysym: 0x42});
f00b6fb6 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;
80cb8ffd
PO
795 expect(evt).to.be.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42});
796 })({type: 'keydown', code: 'KeyA', keysym: 0x42});
f00b6fb6 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:
80cb8ffd 804 expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe9});
f00b6fb6 805 break;
806 case 1:
80cb8ffd 807 expect(evt).to.be.deep.equal({type: 'keyup', code: 'Unidentified', keysym: 0xffe3});
f00b6fb6 808 break;
809 case 2:
80cb8ffd 810 expect(evt).to.be.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42, escape: [0xffe9, 0xffe3]});
f00b6fb6 811 break;
812 case 3:
80cb8ffd 813 expect(evt).to.be.deep.equal({type: 'keydown', code: 'Unidentified', keysym: 0xffe9});
f00b6fb6 814 break;
815 case 4:
80cb8ffd 816 expect(evt).to.be.deep.equal({type: 'keydown', code: 'Unidentified', keysym: 0xffe3});
f00b6fb6 817 break;
818 }
80cb8ffd 819 })({type: 'keydown', code: 'KeyA', keysym: 0x42, escape: [0xffe9, 0xffe3]});
f00b6fb6 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;
80cb8ffd
PO
829 expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]});
830 })({type: 'keyup', code: 'KeyA', keysym: 0x42, escape: [0xfe03]});
f00b6fb6 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;
80cb8ffd
PO
838 expect(evt).to.be.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x42});
839 })({type: 'keyup', code: 'KeyA', keysym: 0x42});
f00b6fb6 840 expect(times_called).to.be.equal(1);
841 });
842 });
843 });
844});