]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.keyboard.js
Temporarily disable SVG favicon
[mirror_novnc.git] / tests / test.keyboard.js
CommitLineData
f00b6fb6 1var assert = chai.assert;
2var expect = chai.expect;
3
0aaf59c2
SM
4import sinon from '../vendor/sinon.js';
5
c1e2785f 6import Keyboard from '../core/input/keyboard.js';
dfae3209 7
099eb856
PO
8function isIE() {
9 return navigator && !!(/trident/i).exec(navigator.userAgent);
10}
11function isEdge() {
12 return navigator && !!(/edge/i).exec(navigator.userAgent);
13}
14
31f169e8 15/* jshint newcap: false, expr: true */
f7363fd2 16describe('Key Event Handling', function() {
f00b6fb6 17 "use strict";
f00b6fb6 18
f7363fd2
PO
19 // The real KeyboardEvent constructor might not work everywhere we
20 // want to run these tests
21 function keyevent(typeArg, KeyboardEventInit) {
22 var e = { type: typeArg };
23 for (var key in KeyboardEventInit) {
24 e[key] = KeyboardEventInit[key];
25 }
26 e.stopPropagation = sinon.spy();
27 e.preventDefault = sinon.spy();
28 return e;
29 };
f00b6fb6 30
f7363fd2
PO
31 describe('Decode Keyboard Events', function() {
32 it('should decode keydown events', function(done) {
099eb856 33 if (isIE() || isEdge()) this.skip();
747b4623
PO
34 var kbd = new Keyboard(document);
35 kbd.onkeyevent = function(keysym, code, down) {
f7363fd2
PO
36 expect(keysym).to.be.equal(0x61);
37 expect(code).to.be.equal('KeyA');
38 expect(down).to.be.equal(true);
39 done();
747b4623 40 };
f7363fd2
PO
41 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
42 });
43 it('should decode keyup events', function(done) {
099eb856 44 if (isIE() || isEdge()) this.skip();
f7363fd2 45 var calls = 0;
747b4623
PO
46 var kbd = new Keyboard(document);
47 kbd.onkeyevent = function(keysym, code, down) {
f7363fd2
PO
48 expect(keysym).to.be.equal(0x61);
49 expect(code).to.be.equal('KeyA');
50 if (calls++ === 1) {
51 expect(down).to.be.equal(false);
52 done();
53 }
747b4623 54 };
f7363fd2
PO
55 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
56 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
57 });
58
59 describe('Legacy keypress Events', function() {
60 it('should wait for keypress when needed', function() {
747b4623
PO
61 var kbd = new Keyboard(document);
62 kbd.onkeyevent = sinon.spy();
f7363fd2 63 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
747b4623 64 expect(kbd.onkeyevent).to.not.have.been.called;
f7363fd2
PO
65 });
66 it('should decode keypress events', function(done) {
747b4623
PO
67 var kbd = new Keyboard(document);
68 kbd.onkeyevent = function(keysym, code, down) {
f7363fd2
PO
69 expect(keysym).to.be.equal(0x61);
70 expect(code).to.be.equal('KeyA');
71 expect(down).to.be.equal(true);
72 done();
747b4623 73 };
f7363fd2
PO
74 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
75 kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61}));
f00b6fb6 76 });
9fce233d 77 it('should ignore keypress with different code', function() {
747b4623
PO
78 var kbd = new Keyboard(document);
79 kbd.onkeyevent = sinon.spy();
9fce233d
PO
80 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
81 kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61}));
747b4623 82 expect(kbd.onkeyevent).to.not.have.been.called;
9fce233d
PO
83 });
84 it('should handle keypress with missing code', function(done) {
747b4623
PO
85 var kbd = new Keyboard(document);
86 kbd.onkeyevent = function(keysym, code, down) {
9fce233d
PO
87 expect(keysym).to.be.equal(0x61);
88 expect(code).to.be.equal('KeyA');
89 expect(down).to.be.equal(true);
90 done();
747b4623 91 };
9fce233d
PO
92 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
93 kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61}));
94 });
7cac5c8e 95 it('should guess key if no keypress and numeric key', function(done) {
747b4623
PO
96 var kbd = new Keyboard(document);
97 kbd.onkeyevent = function(keysym, code, down) {
7cac5c8e
PO
98 expect(keysym).to.be.equal(0x32);
99 expect(code).to.be.equal('Digit2');
100 expect(down).to.be.equal(true);
101 done();
747b4623 102 };
7cac5c8e
PO
103 kbd._handleKeyDown(keyevent('keydown', {code: 'Digit2', keyCode: 0x32}));
104 });
105 it('should guess key if no keypress and alpha key', function(done) {
747b4623
PO
106 var kbd = new Keyboard(document);
107 kbd.onkeyevent = function(keysym, code, down) {
7cac5c8e
PO
108 expect(keysym).to.be.equal(0x61);
109 expect(code).to.be.equal('KeyA');
110 expect(down).to.be.equal(true);
111 done();
747b4623 112 };
7cac5c8e
PO
113 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: false}));
114 });
115 it('should guess key if no keypress and alpha key (with shift)', function(done) {
747b4623
PO
116 var kbd = new Keyboard(document);
117 kbd.onkeyevent = function(keysym, code, down) {
7cac5c8e
PO
118 expect(keysym).to.be.equal(0x41);
119 expect(code).to.be.equal('KeyA');
120 expect(down).to.be.equal(true);
121 done();
747b4623 122 };
7cac5c8e
PO
123 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: true}));
124 });
125 it('should not guess key if no keypress and unknown key', function(done) {
747b4623
PO
126 var kbd = new Keyboard(document);
127 kbd.onkeyevent = function(keysym, code, down) {
7cac5c8e
PO
128 expect(keysym).to.be.equal(0);
129 expect(code).to.be.equal('KeyA');
130 expect(down).to.be.equal(true);
131 done();
747b4623 132 };
7cac5c8e
PO
133 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x09}));
134 });
f00b6fb6 135 });
f00b6fb6 136
f7363fd2 137 describe('suppress the right events at the right time', function() {
099eb856
PO
138 beforeEach(function () {
139 if (isIE() || isEdge()) this.skip();
140 });
f7363fd2 141 it('should suppress anything with a valid key', function() {
3d7bb020 142 var kbd = new Keyboard(document, {});
f7363fd2
PO
143 var evt = keyevent('keydown', {code: 'KeyA', key: 'a'});
144 kbd._handleKeyDown(evt);
145 expect(evt.preventDefault).to.have.been.called;
146 evt = keyevent('keyup', {code: 'KeyA', key: 'a'});
147 kbd._handleKeyUp(evt);
148 expect(evt.preventDefault).to.have.been.called;
149 });
150 it('should not suppress keys without key', function() {
3d7bb020 151 var kbd = new Keyboard(document, {});
f7363fd2
PO
152 var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
153 kbd._handleKeyDown(evt);
154 expect(evt.preventDefault).to.not.have.been.called;
155 });
156 it('should suppress the following keypress event', function() {
3d7bb020 157 var kbd = new Keyboard(document, {});
f7363fd2
PO
158 var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
159 kbd._handleKeyDown(evt);
160 var evt = keyevent('keypress', {code: 'KeyA', charCode: 0x41});
161 kbd._handleKeyPress(evt);
162 expect(evt.preventDefault).to.have.been.called;
f00b6fb6 163 });
164 });
f00b6fb6 165 });
166
9e99ce12
PO
167 describe('Fake keyup', function() {
168 it('should fake keyup events for virtual keyboards', function(done) {
169 if (isIE() || isEdge()) this.skip();
170 var count = 0;
747b4623
PO
171 var kbd = new Keyboard(document);
172 kbd.onkeyevent = function(keysym, code, down) {
9e99ce12
PO
173 switch (count++) {
174 case 0:
175 expect(keysym).to.be.equal(0x61);
176 expect(code).to.be.equal('Unidentified');
177 expect(down).to.be.equal(true);
178 break;
179 case 1:
180 expect(keysym).to.be.equal(0x61);
181 expect(code).to.be.equal('Unidentified');
182 expect(down).to.be.equal(false);
183 done();
184 }
747b4623 185 };
9e99ce12
PO
186 kbd._handleKeyDown(keyevent('keydown', {code: 'Unidentified', key: 'a'}));
187 });
188
189 describe('iOS', function() {
190 var origNavigator;
191 beforeEach(function () {
192 // window.navigator is a protected read-only property in many
193 // environments, so we need to redefine it whilst running these
194 // tests.
195 origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
196 if (origNavigator === undefined) {
197 // Object.getOwnPropertyDescriptor() doesn't work
198 // properly in any version of IE
199 this.skip();
200 }
201
202 Object.defineProperty(window, "navigator", {value: {}});
203 if (window.navigator.platform !== undefined) {
204 // Object.defineProperty() doesn't work properly in old
205 // versions of Chrome
206 this.skip();
207 }
208
209 window.navigator.platform = "iPhone 9.0";
210 });
211 afterEach(function () {
212 Object.defineProperty(window, "navigator", origNavigator);
213 });
214
215 it('should fake keyup events on iOS', function(done) {
216 if (isIE() || isEdge()) this.skip();
217 var count = 0;
747b4623
PO
218 var kbd = new Keyboard(document);
219 kbd.onkeyevent = function(keysym, code, down) {
9e99ce12
PO
220 switch (count++) {
221 case 0:
222 expect(keysym).to.be.equal(0x61);
223 expect(code).to.be.equal('KeyA');
224 expect(down).to.be.equal(true);
225 break;
226 case 1:
227 expect(keysym).to.be.equal(0x61);
228 expect(code).to.be.equal('KeyA');
229 expect(down).to.be.equal(false);
230 done();
231 }
747b4623 232 };
9e99ce12
PO
233 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
234 });
235 });
236 });
237
f00b6fb6 238 describe('Track Key State', function() {
099eb856
PO
239 beforeEach(function () {
240 if (isIE() || isEdge()) this.skip();
241 });
f7363fd2 242 it('should send release using the same keysym as the press', function(done) {
747b4623
PO
243 var kbd = new Keyboard(document);
244 kbd.onkeyevent = function(keysym, code, down) {
f7363fd2
PO
245 expect(keysym).to.be.equal(0x61);
246 expect(code).to.be.equal('KeyA');
247 if (!down) {
248 done();
249 }
747b4623 250 };
f7363fd2
PO
251 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
252 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'}));
f00b6fb6 253 });
ae820533
PO
254 it('should send the same keysym for multiple presses', function() {
255 var count = 0;
747b4623
PO
256 var kbd = new Keyboard(document);
257 kbd.onkeyevent = function(keysym, code, down) {
ae820533
PO
258 expect(keysym).to.be.equal(0x61);
259 expect(code).to.be.equal('KeyA');
260 expect(down).to.be.equal(true);
261 count++;
747b4623 262 };
ae820533
PO
263 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
264 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'}));
265 expect(count).to.be.equal(2);
266 });
f7363fd2 267 it('should do nothing on keyup events if no keys are down', function() {
747b4623
PO
268 var kbd = new Keyboard(document);
269 kbd.onkeyevent = sinon.spy();
f7363fd2 270 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
747b4623 271 expect(kbd.onkeyevent).to.not.have.been.called;
f7363fd2 272 });
7e79dfe4
PO
273
274 describe('Legacy Events', function() {
275 it('should track keys using keyCode if no code', function(done) {
747b4623
PO
276 var kbd = new Keyboard(document);
277 kbd.onkeyevent = function(keysym, code, down) {
7e79dfe4
PO
278 expect(keysym).to.be.equal(0x61);
279 expect(code).to.be.equal('Platform65');
280 if (!down) {
281 done();
282 }
747b4623 283 };
7e79dfe4
PO
284 kbd._handleKeyDown(keyevent('keydown', {keyCode: 65, key: 'a'}));
285 kbd._handleKeyUp(keyevent('keyup', {keyCode: 65, key: 'b'}));
286 });
4093c37f 287 it('should ignore compositing code', function() {
747b4623
PO
288 var kbd = new Keyboard(document);
289 kbd.onkeyevent = function(keysym, code, down) {
4093c37f
PO
290 expect(keysym).to.be.equal(0x61);
291 expect(code).to.be.equal('Unidentified');
747b4623 292 };
4093c37f
PO
293 kbd._handleKeyDown(keyevent('keydown', {keyCode: 229, key: 'a'}));
294 });
7e79dfe4 295 it('should track keys using keyIdentifier if no code', function(done) {
747b4623
PO
296 var kbd = new Keyboard(document);
297 kbd.onkeyevent = function(keysym, code, down) {
7e79dfe4
PO
298 expect(keysym).to.be.equal(0x61);
299 expect(code).to.be.equal('Platform65');
300 if (!down) {
301 done();
302 }
747b4623 303 };
7e79dfe4
PO
304 kbd._handleKeyDown(keyevent('keydown', {keyIdentifier: 'U+0041', key: 'a'}));
305 kbd._handleKeyUp(keyevent('keyup', {keyIdentifier: 'U+0041', key: 'b'}));
306 });
307 });
f7363fd2 308 });
f00b6fb6 309
bf43c263
PO
310 describe('Shuffle modifiers on macOS', function() {
311 var origNavigator;
312 beforeEach(function () {
313 // window.navigator is a protected read-only property in many
314 // environments, so we need to redefine it whilst running these
315 // tests.
316 origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
317 if (origNavigator === undefined) {
318 // Object.getOwnPropertyDescriptor() doesn't work
319 // properly in any version of IE
320 this.skip();
321 }
322
323 Object.defineProperty(window, "navigator", {value: {}});
324 if (window.navigator.platform !== undefined) {
325 // Object.defineProperty() doesn't work properly in old
326 // versions of Chrome
327 this.skip();
328 }
329
330 window.navigator.platform = "Mac x86_64";
331 });
332 afterEach(function () {
333 Object.defineProperty(window, "navigator", origNavigator);
334 });
335
336 it('should change Alt to AltGraph', function() {
337 var count = 0;
747b4623
PO
338 var kbd = new Keyboard(document);
339 kbd.onkeyevent = function(keysym, code, down) {
bf43c263
PO
340 switch (count++) {
341 case 0:
342 expect(keysym).to.be.equal(0xFF7E);
343 expect(code).to.be.equal('AltLeft');
344 break;
345 case 1:
346 expect(keysym).to.be.equal(0xFE03);
347 expect(code).to.be.equal('AltRight');
348 break;
349 }
747b4623 350 };
9782d4a3
PO
351 kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
352 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
bf43c263
PO
353 expect(count).to.be.equal(2);
354 });
355 it('should change left Super to Alt', function(done) {
747b4623
PO
356 var kbd = new Keyboard(document);
357 kbd.onkeyevent = function(keysym, code, down) {
bf43c263
PO
358 expect(keysym).to.be.equal(0xFFE9);
359 expect(code).to.be.equal('MetaLeft');
360 done();
747b4623 361 };
9782d4a3 362 kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1}));
bf43c263
PO
363 });
364 it('should change right Super to left Super', function(done) {
747b4623
PO
365 var kbd = new Keyboard(document);
366 kbd.onkeyevent = function(keysym, code, down) {
bf43c263
PO
367 expect(keysym).to.be.equal(0xFFEB);
368 expect(code).to.be.equal('MetaRight');
369 done();
747b4623 370 };
9782d4a3 371 kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2}));
bf43c263
PO
372 });
373 });
374
375 describe('Escape AltGraph on Windows', function() {
f7363fd2
PO
376 var origNavigator;
377 beforeEach(function () {
378 // window.navigator is a protected read-only property in many
379 // environments, so we need to redefine it whilst running these
380 // tests.
381 origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
382 if (origNavigator === undefined) {
383 // Object.getOwnPropertyDescriptor() doesn't work
384 // properly in any version of IE
385 this.skip();
386 }
387
388 Object.defineProperty(window, "navigator", {value: {}});
389 if (window.navigator.platform !== undefined) {
390 // Object.defineProperty() doesn't work properly in old
391 // versions of Chrome
392 this.skip();
393 }
394
395 window.navigator.platform = "Windows x86_64";
396 });
397 afterEach(function () {
398 Object.defineProperty(window, "navigator", origNavigator);
399 });
400
bf43c263 401 it('should generate fake undo/redo events on press when AltGraph is down', function() {
f00b6fb6 402 var times_called = 0;
747b4623
PO
403 var kbd = new Keyboard(document);
404 kbd.onkeyevent = function(keysym, code, down) {
f7363fd2
PO
405 switch(times_called++) {
406 case 0:
407 expect(keysym).to.be.equal(0xFFE3);
408 expect(code).to.be.equal('ControlLeft');
409 expect(down).to.be.equal(true);
410 break;
411 case 1:
bf43c263
PO
412 expect(keysym).to.be.equal(0xFFEA);
413 expect(code).to.be.equal('AltRight');
f7363fd2
PO
414 expect(down).to.be.equal(true);
415 break;
416 case 2:
bf43c263
PO
417 expect(keysym).to.be.equal(0xFFEA);
418 expect(code).to.be.equal('AltRight');
f7363fd2
PO
419 expect(down).to.be.equal(false);
420 break;
421 case 3:
422 expect(keysym).to.be.equal(0xFFE3);
bf43c263 423 expect(code).to.be.equal('ControlLeft');
f7363fd2
PO
424 expect(down).to.be.equal(false);
425 break;
426 case 4:
427 expect(keysym).to.be.equal(0x61);
428 expect(code).to.be.equal('KeyA');
429 expect(down).to.be.equal(true);
430 break;
431 case 5:
bf43c263
PO
432 expect(keysym).to.be.equal(0xFFE3);
433 expect(code).to.be.equal('ControlLeft');
f7363fd2
PO
434 expect(down).to.be.equal(true);
435 break;
436 case 6:
bf43c263
PO
437 expect(keysym).to.be.equal(0xFFEA);
438 expect(code).to.be.equal('AltRight');
f7363fd2
PO
439 expect(down).to.be.equal(true);
440 break;
441 }
747b4623 442 };
f7363fd2 443 // First the modifier combo
9782d4a3
PO
444 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
445 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
f7363fd2
PO
446 // Next a normal character
447 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
448 expect(times_called).to.be.equal(7);
449 });
450 it('should no do anything on key release', function() {
f00b6fb6 451 var times_called = 0;
747b4623
PO
452 var kbd = new Keyboard(document);
453 kbd.onkeyevent = function(keysym, code, down) {
f7363fd2
PO
454 switch(times_called++) {
455 case 7:
456 expect(keysym).to.be.equal(0x61);
457 expect(code).to.be.equal('KeyA');
458 expect(down).to.be.equal(false);
459 break;
460 }
747b4623 461 };
f7363fd2 462 // First the modifier combo
9782d4a3
PO
463 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
464 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
f7363fd2
PO
465 // Next a normal character
466 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
467 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
468 expect(times_called).to.be.equal(8);
469 });
470 it('should not consider a char modifier to be down on the modifier key itself', function() {
f00b6fb6 471 var times_called = 0;
747b4623
PO
472 var kbd = new Keyboard(document);
473 kbd.onkeyevent = function(keysym, code, down) {
f7363fd2
PO
474 switch(times_called++) {
475 case 0:
476 expect(keysym).to.be.equal(0xFFE3);
477 expect(code).to.be.equal('ControlLeft');
478 expect(down).to.be.equal(true);
479 break;
480 case 1:
481 expect(keysym).to.be.equal(0xFFE9);
482 expect(code).to.be.equal('AltLeft');
483 expect(down).to.be.equal(true);
484 break;
485 case 2:
486 expect(keysym).to.be.equal(0xFFE3);
487 expect(code).to.be.equal('ControlLeft');
488 expect(down).to.be.equal(true);
489 break;
490 }
747b4623 491 };
f7363fd2 492 // First the modifier combo
9782d4a3
PO
493 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
494 kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
f7363fd2 495 // Then one of the keys again
9782d4a3 496 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
f00b6fb6 497 expect(times_called).to.be.equal(3);
f00b6fb6 498 });
499 });
500});