]> git.proxmox.com Git - mirror_novnc.git/blob - tests/test.keyboard.js
Merge branch 'qemufix' of https://github.com/CendioOssman/noVNC
[mirror_novnc.git] / tests / test.keyboard.js
1 var assert = chai.assert;
2 var expect = chai.expect;
3
4 import { Keyboard } from '../core/input/devices.js';
5 import keysyms from '../core/input/keysymdef.js';
6 import * as KeyboardUtil from '../core/input/util.js';
7
8 function isIE() {
9 return navigator && !!(/trident/i).exec(navigator.userAgent);
10 }
11 function isEdge() {
12 return navigator && !!(/edge/i).exec(navigator.userAgent);
13 }
14
15 /* jshint newcap: false, expr: true */
16 describe('Key Event Handling', function() {
17 "use strict";
18
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 };
30
31 describe('Decode Keyboard Events', function() {
32 it('should decode keydown events', function(done) {
33 if (isIE() || isEdge()) this.skip();
34 var kbd = new Keyboard({
35 onKeyEvent: function(keysym, code, down) {
36 expect(keysym).to.be.equal(0x61);
37 expect(code).to.be.equal('KeyA');
38 expect(down).to.be.equal(true);
39 done();
40 }});
41 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
42 });
43 it('should decode keyup events', function(done) {
44 if (isIE() || isEdge()) this.skip();
45 var calls = 0;
46 var kbd = new Keyboard({
47 onKeyEvent: function(keysym, code, down) {
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 }
54 }});
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() {
61 var callback = sinon.spy();
62 var kbd = new Keyboard({onKeyEvent: callback});
63 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
64 expect(callback).to.not.have.been.called;
65 });
66 it('should decode keypress events', function(done) {
67 var kbd = new Keyboard({
68 onKeyEvent: function(keysym, code, down) {
69 expect(keysym).to.be.equal(0x61);
70 expect(code).to.be.equal('KeyA');
71 expect(down).to.be.equal(true);
72 done();
73 }});
74 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
75 kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61}));
76 });
77 it('should ignore keypress with different code', function() {
78 var callback = sinon.spy();
79 var kbd = new Keyboard({onKeyEvent: callback});
80 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
81 kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61}));
82 expect(callback).to.not.have.been.called;
83 });
84 it('should handle keypress with missing code', function(done) {
85 var kbd = new Keyboard({
86 onKeyEvent: function(keysym, code, down) {
87 expect(keysym).to.be.equal(0x61);
88 expect(code).to.be.equal('KeyA');
89 expect(down).to.be.equal(true);
90 done();
91 }});
92 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
93 kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61}));
94 });
95 it('should guess key if no keypress and numeric key', function(done) {
96 var kbd = new Keyboard({
97 onKeyEvent: function(keysym, code, down) {
98 expect(keysym).to.be.equal(0x32);
99 expect(code).to.be.equal('Digit2');
100 expect(down).to.be.equal(true);
101 done();
102 }});
103 kbd._handleKeyDown(keyevent('keydown', {code: 'Digit2', keyCode: 0x32}));
104 });
105 it('should guess key if no keypress and alpha key', function(done) {
106 var kbd = new Keyboard({
107 onKeyEvent: function(keysym, code, down) {
108 expect(keysym).to.be.equal(0x61);
109 expect(code).to.be.equal('KeyA');
110 expect(down).to.be.equal(true);
111 done();
112 }});
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) {
116 var kbd = new Keyboard({
117 onKeyEvent: function(keysym, code, down) {
118 expect(keysym).to.be.equal(0x41);
119 expect(code).to.be.equal('KeyA');
120 expect(down).to.be.equal(true);
121 done();
122 }});
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) {
126 var kbd = new Keyboard({
127 onKeyEvent: function(keysym, code, down) {
128 expect(keysym).to.be.equal(0);
129 expect(code).to.be.equal('KeyA');
130 expect(down).to.be.equal(true);
131 done();
132 }});
133 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x09}));
134 });
135 });
136
137 describe('suppress the right events at the right time', function() {
138 beforeEach(function () {
139 if (isIE() || isEdge()) this.skip();
140 });
141 it('should suppress anything with a valid key', function() {
142 var kbd = new Keyboard({});
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() {
151 var kbd = new Keyboard({});
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() {
157 var kbd = new Keyboard({});
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;
163 });
164 });
165 });
166
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;
171 var kbd = new Keyboard({
172 onKeyEvent: function(keysym, code, down) {
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 }
185 }});
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;
218 var kbd = new Keyboard({
219 onKeyEvent: function(keysym, code, down) {
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 }
232 }});
233 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
234 });
235 });
236 });
237
238 describe('Track Key State', function() {
239 beforeEach(function () {
240 if (isIE() || isEdge()) this.skip();
241 });
242 it('should send release using the same keysym as the press', function(done) {
243 var kbd = new Keyboard({
244 onKeyEvent: function(keysym, code, down) {
245 expect(keysym).to.be.equal(0x61);
246 expect(code).to.be.equal('KeyA');
247 if (!down) {
248 done();
249 }
250 }});
251 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
252 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'}));
253 });
254 it('should send the same keysym for multiple presses', function() {
255 var count = 0;
256 var kbd = new Keyboard({
257 onKeyEvent: function(keysym, code, down) {
258 expect(keysym).to.be.equal(0x61);
259 expect(code).to.be.equal('KeyA');
260 expect(down).to.be.equal(true);
261 count++;
262 }});
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 });
267 it('should do nothing on keyup events if no keys are down', function() {
268 var callback = sinon.spy();
269 var kbd = new Keyboard({onKeyEvent: callback});
270 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
271 expect(callback).to.not.have.been.called;
272 });
273
274 describe('Legacy Events', function() {
275 it('should track keys using keyCode if no code', function(done) {
276 var kbd = new Keyboard({
277 onKeyEvent: function(keysym, code, down) {
278 expect(keysym).to.be.equal(0x61);
279 expect(code).to.be.equal('Platform65');
280 if (!down) {
281 done();
282 }
283 }});
284 kbd._handleKeyDown(keyevent('keydown', {keyCode: 65, key: 'a'}));
285 kbd._handleKeyUp(keyevent('keyup', {keyCode: 65, key: 'b'}));
286 });
287 it('should track keys using keyIdentifier if no code', function(done) {
288 var kbd = new Keyboard({
289 onKeyEvent: function(keysym, code, down) {
290 expect(keysym).to.be.equal(0x61);
291 expect(code).to.be.equal('Platform65');
292 if (!down) {
293 done();
294 }
295 }});
296 kbd._handleKeyDown(keyevent('keydown', {keyIdentifier: 'U+0041', key: 'a'}));
297 kbd._handleKeyUp(keyevent('keyup', {keyIdentifier: 'U+0041', key: 'b'}));
298 });
299 });
300 });
301
302 describe('Shuffle modifiers on macOS', function() {
303 var origNavigator;
304 beforeEach(function () {
305 // window.navigator is a protected read-only property in many
306 // environments, so we need to redefine it whilst running these
307 // tests.
308 origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
309 if (origNavigator === undefined) {
310 // Object.getOwnPropertyDescriptor() doesn't work
311 // properly in any version of IE
312 this.skip();
313 }
314
315 Object.defineProperty(window, "navigator", {value: {}});
316 if (window.navigator.platform !== undefined) {
317 // Object.defineProperty() doesn't work properly in old
318 // versions of Chrome
319 this.skip();
320 }
321
322 window.navigator.platform = "Mac x86_64";
323 });
324 afterEach(function () {
325 Object.defineProperty(window, "navigator", origNavigator);
326 });
327
328 it('should change Alt to AltGraph', function() {
329 var count = 0;
330 var kbd = new Keyboard({
331 onKeyEvent: function(keysym, code, down) {
332 switch (count++) {
333 case 0:
334 expect(keysym).to.be.equal(0xFF7E);
335 expect(code).to.be.equal('AltLeft');
336 break;
337 case 1:
338 expect(keysym).to.be.equal(0xFE03);
339 expect(code).to.be.equal('AltRight');
340 break;
341 }
342 }});
343 kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
344 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
345 expect(count).to.be.equal(2);
346 });
347 it('should change left Super to Alt', function(done) {
348 var kbd = new Keyboard({
349 onKeyEvent: function(keysym, code, down) {
350 expect(keysym).to.be.equal(0xFFE9);
351 expect(code).to.be.equal('MetaLeft');
352 done();
353 }});
354 kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1}));
355 });
356 it('should change right Super to left Super', function(done) {
357 var kbd = new Keyboard({
358 onKeyEvent: function(keysym, code, down) {
359 expect(keysym).to.be.equal(0xFFEB);
360 expect(code).to.be.equal('MetaRight');
361 done();
362 }});
363 kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2}));
364 });
365 });
366
367 describe('Escape AltGraph on Windows', function() {
368 var origNavigator;
369 beforeEach(function () {
370 // window.navigator is a protected read-only property in many
371 // environments, so we need to redefine it whilst running these
372 // tests.
373 origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
374 if (origNavigator === undefined) {
375 // Object.getOwnPropertyDescriptor() doesn't work
376 // properly in any version of IE
377 this.skip();
378 }
379
380 Object.defineProperty(window, "navigator", {value: {}});
381 if (window.navigator.platform !== undefined) {
382 // Object.defineProperty() doesn't work properly in old
383 // versions of Chrome
384 this.skip();
385 }
386
387 window.navigator.platform = "Windows x86_64";
388 });
389 afterEach(function () {
390 Object.defineProperty(window, "navigator", origNavigator);
391 });
392
393 it('should generate fake undo/redo events on press when AltGraph is down', function() {
394 var times_called = 0;
395 var kbd = new Keyboard({
396 onKeyEvent: function(keysym, code, down) {
397 switch(times_called++) {
398 case 0:
399 expect(keysym).to.be.equal(0xFFE3);
400 expect(code).to.be.equal('ControlLeft');
401 expect(down).to.be.equal(true);
402 break;
403 case 1:
404 expect(keysym).to.be.equal(0xFFEA);
405 expect(code).to.be.equal('AltRight');
406 expect(down).to.be.equal(true);
407 break;
408 case 2:
409 expect(keysym).to.be.equal(0xFFEA);
410 expect(code).to.be.equal('AltRight');
411 expect(down).to.be.equal(false);
412 break;
413 case 3:
414 expect(keysym).to.be.equal(0xFFE3);
415 expect(code).to.be.equal('ControlLeft');
416 expect(down).to.be.equal(false);
417 break;
418 case 4:
419 expect(keysym).to.be.equal(0x61);
420 expect(code).to.be.equal('KeyA');
421 expect(down).to.be.equal(true);
422 break;
423 case 5:
424 expect(keysym).to.be.equal(0xFFE3);
425 expect(code).to.be.equal('ControlLeft');
426 expect(down).to.be.equal(true);
427 break;
428 case 6:
429 expect(keysym).to.be.equal(0xFFEA);
430 expect(code).to.be.equal('AltRight');
431 expect(down).to.be.equal(true);
432 break;
433 }
434 }});
435 // First the modifier combo
436 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
437 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
438 // Next a normal character
439 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
440 expect(times_called).to.be.equal(7);
441 });
442 it('should no do anything on key release', function() {
443 var times_called = 0;
444 var kbd = new Keyboard({
445 onKeyEvent: function(keysym, code, down) {
446 switch(times_called++) {
447 case 7:
448 expect(keysym).to.be.equal(0x61);
449 expect(code).to.be.equal('KeyA');
450 expect(down).to.be.equal(false);
451 break;
452 }
453 }});
454 // First the modifier combo
455 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
456 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
457 // Next a normal character
458 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
459 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
460 expect(times_called).to.be.equal(8);
461 });
462 it('should not consider a char modifier to be down on the modifier key itself', function() {
463 var times_called = 0;
464 var kbd = new Keyboard({
465 onKeyEvent: function(keysym, code, down) {
466 switch(times_called++) {
467 case 0:
468 expect(keysym).to.be.equal(0xFFE3);
469 expect(code).to.be.equal('ControlLeft');
470 expect(down).to.be.equal(true);
471 break;
472 case 1:
473 expect(keysym).to.be.equal(0xFFE9);
474 expect(code).to.be.equal('AltLeft');
475 expect(down).to.be.equal(true);
476 break;
477 case 2:
478 expect(keysym).to.be.equal(0xFFE3);
479 expect(code).to.be.equal('ControlLeft');
480 expect(down).to.be.equal(true);
481 break;
482 }
483 }});
484 // First the modifier combo
485 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
486 kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
487 // Then one of the keys again
488 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
489 expect(times_called).to.be.equal(3);
490 });
491 });
492 });