]> git.proxmox.com Git - mirror_novnc.git/blame - tests/test.keyboard.js
Drop support for Internet Explorer
[mirror_novnc.git] / tests / test.keyboard.js
CommitLineData
2b5f94fa 1const expect = chai.expect;
f00b6fb6 2
c1e2785f 3import Keyboard from '../core/input/keyboard.js';
59ef2916 4import * as browser from '../core/util/browser.js';
099eb856 5
2c5491e1 6describe('Key Event Handling', function () {
f00b6fb6 7 "use strict";
f00b6fb6 8
f7363fd2
PO
9 // The real KeyboardEvent constructor might not work everywhere we
10 // want to run these tests
11 function keyevent(typeArg, KeyboardEventInit) {
2b5f94fa
JD
12 const e = { type: typeArg };
13 for (let key in KeyboardEventInit) {
f7363fd2
PO
14 e[key] = KeyboardEventInit[key];
15 }
16 e.stopPropagation = sinon.spy();
17 e.preventDefault = sinon.spy();
18 return e;
8727f598 19 }
f00b6fb6 20
2c5491e1
PO
21 describe('Decode Keyboard Events', function () {
22 it('should decode keydown events', function (done) {
c01eb5e7 23 if (browser.isEdge()) this.skip();
2b5f94fa 24 const kbd = new Keyboard(document);
651c23ec 25 kbd.onkeyevent = (keysym, code, down) => {
f7363fd2
PO
26 expect(keysym).to.be.equal(0x61);
27 expect(code).to.be.equal('KeyA');
28 expect(down).to.be.equal(true);
29 done();
747b4623 30 };
f7363fd2
PO
31 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
32 });
2c5491e1 33 it('should decode keyup events', function (done) {
c01eb5e7 34 if (browser.isEdge()) this.skip();
2b5f94fa
JD
35 let calls = 0;
36 const kbd = new Keyboard(document);
651c23ec 37 kbd.onkeyevent = (keysym, code, down) => {
f7363fd2
PO
38 expect(keysym).to.be.equal(0x61);
39 expect(code).to.be.equal('KeyA');
40 if (calls++ === 1) {
41 expect(down).to.be.equal(false);
42 done();
43 }
747b4623 44 };
f7363fd2
PO
45 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
46 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
47 });
48
2c5491e1
PO
49 describe('Legacy keypress Events', function () {
50 it('should wait for keypress when needed', function () {
2b5f94fa 51 const kbd = new Keyboard(document);
747b4623 52 kbd.onkeyevent = sinon.spy();
f7363fd2 53 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
747b4623 54 expect(kbd.onkeyevent).to.not.have.been.called;
f7363fd2 55 });
2c5491e1 56 it('should decode keypress events', function (done) {
2b5f94fa 57 const kbd = new Keyboard(document);
651c23ec 58 kbd.onkeyevent = (keysym, code, down) => {
f7363fd2
PO
59 expect(keysym).to.be.equal(0x61);
60 expect(code).to.be.equal('KeyA');
61 expect(down).to.be.equal(true);
62 done();
747b4623 63 };
f7363fd2
PO
64 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
65 kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61}));
f00b6fb6 66 });
2c5491e1 67 it('should ignore keypress with different code', function () {
2b5f94fa 68 const kbd = new Keyboard(document);
747b4623 69 kbd.onkeyevent = sinon.spy();
9fce233d
PO
70 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
71 kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61}));
747b4623 72 expect(kbd.onkeyevent).to.not.have.been.called;
9fce233d 73 });
2c5491e1 74 it('should handle keypress with missing code', function (done) {
2b5f94fa 75 const kbd = new Keyboard(document);
651c23ec 76 kbd.onkeyevent = (keysym, code, down) => {
9fce233d
PO
77 expect(keysym).to.be.equal(0x61);
78 expect(code).to.be.equal('KeyA');
79 expect(down).to.be.equal(true);
80 done();
747b4623 81 };
9fce233d
PO
82 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
83 kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61}));
84 });
2c5491e1 85 it('should guess key if no keypress and numeric key', function (done) {
2b5f94fa 86 const kbd = new Keyboard(document);
651c23ec 87 kbd.onkeyevent = (keysym, code, down) => {
7cac5c8e
PO
88 expect(keysym).to.be.equal(0x32);
89 expect(code).to.be.equal('Digit2');
90 expect(down).to.be.equal(true);
91 done();
747b4623 92 };
7cac5c8e
PO
93 kbd._handleKeyDown(keyevent('keydown', {code: 'Digit2', keyCode: 0x32}));
94 });
2c5491e1 95 it('should guess key if no keypress and alpha key', function (done) {
2b5f94fa 96 const kbd = new Keyboard(document);
651c23ec 97 kbd.onkeyevent = (keysym, code, down) => {
7cac5c8e
PO
98 expect(keysym).to.be.equal(0x61);
99 expect(code).to.be.equal('KeyA');
100 expect(down).to.be.equal(true);
101 done();
747b4623 102 };
7cac5c8e
PO
103 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: false}));
104 });
2c5491e1 105 it('should guess key if no keypress and alpha key (with shift)', function (done) {
2b5f94fa 106 const kbd = new Keyboard(document);
651c23ec 107 kbd.onkeyevent = (keysym, code, down) => {
7cac5c8e
PO
108 expect(keysym).to.be.equal(0x41);
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: true}));
114 });
2c5491e1 115 it('should not guess key if no keypress and unknown key', function (done) {
2b5f94fa 116 const kbd = new Keyboard(document);
651c23ec 117 kbd.onkeyevent = (keysym, code, down) => {
7cac5c8e
PO
118 expect(keysym).to.be.equal(0);
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: 0x09}));
124 });
f00b6fb6 125 });
f00b6fb6 126
2c5491e1 127 describe('suppress the right events at the right time', function () {
099eb856 128 beforeEach(function () {
c01eb5e7 129 if (browser.isEdge()) this.skip();
099eb856 130 });
2c5491e1 131 it('should suppress anything with a valid key', function () {
2b5f94fa
JD
132 const kbd = new Keyboard(document, {});
133 const evt1 = keyevent('keydown', {code: 'KeyA', key: 'a'});
8727f598
JD
134 kbd._handleKeyDown(evt1);
135 expect(evt1.preventDefault).to.have.been.called;
2b5f94fa 136 const evt2 = keyevent('keyup', {code: 'KeyA', key: 'a'});
8727f598
JD
137 kbd._handleKeyUp(evt2);
138 expect(evt2.preventDefault).to.have.been.called;
f7363fd2 139 });
2c5491e1 140 it('should not suppress keys without key', function () {
2b5f94fa
JD
141 const kbd = new Keyboard(document, {});
142 const evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
f7363fd2
PO
143 kbd._handleKeyDown(evt);
144 expect(evt.preventDefault).to.not.have.been.called;
145 });
2c5491e1 146 it('should suppress the following keypress event', function () {
2b5f94fa
JD
147 const kbd = new Keyboard(document, {});
148 const evt1 = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
8727f598 149 kbd._handleKeyDown(evt1);
2b5f94fa 150 const evt2 = keyevent('keypress', {code: 'KeyA', charCode: 0x41});
8727f598
JD
151 kbd._handleKeyPress(evt2);
152 expect(evt2.preventDefault).to.have.been.called;
f00b6fb6 153 });
154 });
f00b6fb6 155 });
156
2c5491e1
PO
157 describe('Fake keyup', function () {
158 it('should fake keyup events for virtual keyboards', function (done) {
c01eb5e7 159 if (browser.isEdge()) this.skip();
2b5f94fa
JD
160 let count = 0;
161 const kbd = new Keyboard(document);
651c23ec 162 kbd.onkeyevent = (keysym, code, down) => {
9e99ce12
PO
163 switch (count++) {
164 case 0:
165 expect(keysym).to.be.equal(0x61);
166 expect(code).to.be.equal('Unidentified');
167 expect(down).to.be.equal(true);
168 break;
169 case 1:
170 expect(keysym).to.be.equal(0x61);
171 expect(code).to.be.equal('Unidentified');
172 expect(down).to.be.equal(false);
173 done();
174 }
747b4623 175 };
9e99ce12
PO
176 kbd._handleKeyDown(keyevent('keydown', {code: 'Unidentified', key: 'a'}));
177 });
9e99ce12
PO
178 });
179
2c5491e1 180 describe('Track Key State', function () {
099eb856 181 beforeEach(function () {
c01eb5e7 182 if (browser.isEdge()) this.skip();
099eb856 183 });
2c5491e1 184 it('should send release using the same keysym as the press', function (done) {
2b5f94fa 185 const kbd = new Keyboard(document);
651c23ec 186 kbd.onkeyevent = (keysym, code, down) => {
f7363fd2
PO
187 expect(keysym).to.be.equal(0x61);
188 expect(code).to.be.equal('KeyA');
189 if (!down) {
190 done();
191 }
747b4623 192 };
f7363fd2
PO
193 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
194 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'}));
f00b6fb6 195 });
2c5491e1 196 it('should send the same keysym for multiple presses', function () {
2b5f94fa
JD
197 let count = 0;
198 const kbd = new Keyboard(document);
651c23ec 199 kbd.onkeyevent = (keysym, code, down) => {
ae820533
PO
200 expect(keysym).to.be.equal(0x61);
201 expect(code).to.be.equal('KeyA');
202 expect(down).to.be.equal(true);
203 count++;
747b4623 204 };
ae820533
PO
205 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
206 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'}));
207 expect(count).to.be.equal(2);
208 });
2c5491e1 209 it('should do nothing on keyup events if no keys are down', function () {
2b5f94fa 210 const kbd = new Keyboard(document);
747b4623 211 kbd.onkeyevent = sinon.spy();
f7363fd2 212 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
747b4623 213 expect(kbd.onkeyevent).to.not.have.been.called;
f7363fd2 214 });
7e79dfe4 215
2c5491e1
PO
216 describe('Legacy Events', function () {
217 it('should track keys using keyCode if no code', function (done) {
2b5f94fa 218 const kbd = new Keyboard(document);
651c23ec 219 kbd.onkeyevent = (keysym, code, down) => {
7e79dfe4
PO
220 expect(keysym).to.be.equal(0x61);
221 expect(code).to.be.equal('Platform65');
222 if (!down) {
223 done();
224 }
747b4623 225 };
7e79dfe4
PO
226 kbd._handleKeyDown(keyevent('keydown', {keyCode: 65, key: 'a'}));
227 kbd._handleKeyUp(keyevent('keyup', {keyCode: 65, key: 'b'}));
228 });
2c5491e1 229 it('should ignore compositing code', function () {
2b5f94fa 230 const kbd = new Keyboard(document);
651c23ec 231 kbd.onkeyevent = (keysym, code, down) => {
4093c37f
PO
232 expect(keysym).to.be.equal(0x61);
233 expect(code).to.be.equal('Unidentified');
747b4623 234 };
4093c37f
PO
235 kbd._handleKeyDown(keyevent('keydown', {keyCode: 229, key: 'a'}));
236 });
2c5491e1 237 it('should track keys using keyIdentifier if no code', function (done) {
2b5f94fa 238 const kbd = new Keyboard(document);
651c23ec 239 kbd.onkeyevent = (keysym, code, down) => {
7e79dfe4
PO
240 expect(keysym).to.be.equal(0x61);
241 expect(code).to.be.equal('Platform65');
242 if (!down) {
243 done();
244 }
747b4623 245 };
7e79dfe4
PO
246 kbd._handleKeyDown(keyevent('keydown', {keyIdentifier: 'U+0041', key: 'a'}));
247 kbd._handleKeyUp(keyevent('keyup', {keyIdentifier: 'U+0041', key: 'b'}));
248 });
249 });
f7363fd2 250 });
f00b6fb6 251
2c5491e1 252 describe('Shuffle modifiers on macOS', function () {
2b5f94fa 253 let origNavigator;
bf43c263
PO
254 beforeEach(function () {
255 // window.navigator is a protected read-only property in many
256 // environments, so we need to redefine it whilst running these
257 // tests.
258 origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
bf43c263
PO
259
260 Object.defineProperty(window, "navigator", {value: {}});
261 if (window.navigator.platform !== undefined) {
262 // Object.defineProperty() doesn't work properly in old
263 // versions of Chrome
264 this.skip();
265 }
266
267 window.navigator.platform = "Mac x86_64";
268 });
269 afterEach(function () {
eb05b45b
PO
270 if (origNavigator !== undefined) {
271 Object.defineProperty(window, "navigator", origNavigator);
272 }
bf43c263
PO
273 });
274
2c5491e1 275 it('should change Alt to AltGraph', function () {
2b5f94fa
JD
276 let count = 0;
277 const kbd = new Keyboard(document);
651c23ec 278 kbd.onkeyevent = (keysym, code, down) => {
bf43c263
PO
279 switch (count++) {
280 case 0:
281 expect(keysym).to.be.equal(0xFF7E);
282 expect(code).to.be.equal('AltLeft');
283 break;
284 case 1:
285 expect(keysym).to.be.equal(0xFE03);
286 expect(code).to.be.equal('AltRight');
287 break;
288 }
747b4623 289 };
9782d4a3
PO
290 kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
291 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
bf43c263
PO
292 expect(count).to.be.equal(2);
293 });
2c5491e1 294 it('should change left Super to Alt', function (done) {
2b5f94fa 295 const kbd = new Keyboard(document);
651c23ec 296 kbd.onkeyevent = (keysym, code, down) => {
bf43c263
PO
297 expect(keysym).to.be.equal(0xFFE9);
298 expect(code).to.be.equal('MetaLeft');
299 done();
747b4623 300 };
9782d4a3 301 kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1}));
bf43c263 302 });
2c5491e1 303 it('should change right Super to left Super', function (done) {
2b5f94fa 304 const kbd = new Keyboard(document);
651c23ec 305 kbd.onkeyevent = (keysym, code, down) => {
bf43c263
PO
306 expect(keysym).to.be.equal(0xFFEB);
307 expect(code).to.be.equal('MetaRight');
308 done();
747b4623 309 };
9782d4a3 310 kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2}));
bf43c263
PO
311 });
312 });
313
a6304f91
AT
314 describe('Caps Lock on iOS and macOS', function () {
315 let origNavigator;
316 beforeEach(function () {
317 // window.navigator is a protected read-only property in many
318 // environments, so we need to redefine it whilst running these
319 // tests.
320 origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
a6304f91
AT
321
322 Object.defineProperty(window, "navigator", {value: {}});
323 if (window.navigator.platform !== undefined) {
324 // Object.defineProperty() doesn't work properly in old
325 // versions of Chrome
326 this.skip();
327 }
328 });
329
330 afterEach(function () {
eb05b45b
PO
331 if (origNavigator !== undefined) {
332 Object.defineProperty(window, "navigator", origNavigator);
333 }
a6304f91
AT
334 });
335
336 it('should toggle caps lock on key press on iOS', function (done) {
337 window.navigator.platform = "iPad";
338 const kbd = new Keyboard(document);
339 kbd.onkeyevent = sinon.spy();
340 kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));
341
342 expect(kbd.onkeyevent).to.have.been.calledTwice;
343 expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
344 expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
345 done();
346 });
347
348 it('should toggle caps lock on key press on mac', function (done) {
349 window.navigator.platform = "Mac";
350 const kbd = new Keyboard(document);
351 kbd.onkeyevent = sinon.spy();
352 kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));
353
354 expect(kbd.onkeyevent).to.have.been.calledTwice;
355 expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
356 expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
357 done();
358 });
359
360 it('should toggle caps lock on key release on iOS', function (done) {
361 window.navigator.platform = "iPad";
362 const kbd = new Keyboard(document);
363 kbd.onkeyevent = sinon.spy();
364 kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));
365
366 expect(kbd.onkeyevent).to.have.been.calledTwice;
367 expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
368 expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
369 done();
370 });
371
372 it('should toggle caps lock on key release on mac', function (done) {
373 window.navigator.platform = "Mac";
374 const kbd = new Keyboard(document);
375 kbd.onkeyevent = sinon.spy();
376 kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));
377
378 expect(kbd.onkeyevent).to.have.been.calledTwice;
379 expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
380 expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
381 done();
382 });
383 });
384
2c5491e1 385 describe('Escape AltGraph on Windows', function () {
2b5f94fa 386 let origNavigator;
f7363fd2
PO
387 beforeEach(function () {
388 // window.navigator is a protected read-only property in many
389 // environments, so we need to redefine it whilst running these
390 // tests.
391 origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
f7363fd2
PO
392
393 Object.defineProperty(window, "navigator", {value: {}});
394 if (window.navigator.platform !== undefined) {
395 // Object.defineProperty() doesn't work properly in old
396 // versions of Chrome
397 this.skip();
398 }
399
400 window.navigator.platform = "Windows x86_64";
b22c9ef9
PO
401
402 this.clock = sinon.useFakeTimers();
f7363fd2
PO
403 });
404 afterEach(function () {
eb05b45b
PO
405 if (origNavigator !== undefined) {
406 Object.defineProperty(window, "navigator", origNavigator);
407 }
408 if (this.clock !== undefined) {
409 this.clock.restore();
410 }
f7363fd2
PO
411 });
412
b22c9ef9 413 it('should supress ControlLeft until it knows if it is AltGr', function () {
2b5f94fa 414 const kbd = new Keyboard(document);
b22c9ef9 415 kbd.onkeyevent = sinon.spy();
9782d4a3 416 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
b22c9ef9 417 expect(kbd.onkeyevent).to.not.have.been.called;
f7363fd2 418 });
b22c9ef9
PO
419
420 it('should not trigger on repeating ControlLeft', function () {
2b5f94fa 421 const kbd = new Keyboard(document);
b22c9ef9 422 kbd.onkeyevent = sinon.spy();
9782d4a3 423 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
b22c9ef9
PO
424 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
425 expect(kbd.onkeyevent).to.have.been.calledTwice;
426 expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
427 expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
f7363fd2 428 });
b22c9ef9
PO
429
430 it('should not supress ControlRight', function () {
2b5f94fa 431 const kbd = new Keyboard(document);
b22c9ef9
PO
432 kbd.onkeyevent = sinon.spy();
433 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlRight', key: 'Control', location: 2}));
434 expect(kbd.onkeyevent).to.have.been.calledOnce;
435 expect(kbd.onkeyevent).to.have.been.calledWith(0xffe4, "ControlRight", true);
436 });
437
438 it('should release ControlLeft after 100 ms', function () {
2b5f94fa 439 const kbd = new Keyboard(document);
b22c9ef9 440 kbd.onkeyevent = sinon.spy();
9782d4a3 441 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
b22c9ef9
PO
442 expect(kbd.onkeyevent).to.not.have.been.called;
443 this.clock.tick(100);
444 expect(kbd.onkeyevent).to.have.been.calledOnce;
445 expect(kbd.onkeyevent).to.have.been.calledWith(0xffe3, "ControlLeft", true);
446 });
447
448 it('should release ControlLeft on other key press', function () {
2b5f94fa 449 const kbd = new Keyboard(document);
b22c9ef9
PO
450 kbd.onkeyevent = sinon.spy();
451 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
452 expect(kbd.onkeyevent).to.not.have.been.called;
453 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
454 expect(kbd.onkeyevent).to.have.been.calledTwice;
455 expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
456 expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0x61, "KeyA", true);
457
458 // Check that the timer is properly dead
c9765e50 459 kbd.onkeyevent.resetHistory();
b22c9ef9
PO
460 this.clock.tick(100);
461 expect(kbd.onkeyevent).to.not.have.been.called;
462 });
463
464 it('should release ControlLeft on other key release', function () {
2b5f94fa 465 const kbd = new Keyboard(document);
b22c9ef9
PO
466 kbd.onkeyevent = sinon.spy();
467 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
9782d4a3 468 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
b22c9ef9
PO
469 expect(kbd.onkeyevent).to.have.been.calledOnce;
470 expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x61, "KeyA", true);
471 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
472 expect(kbd.onkeyevent).to.have.been.calledThrice;
473 expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
474 expect(kbd.onkeyevent.thirdCall).to.have.been.calledWith(0x61, "KeyA", false);
475
476 // Check that the timer is properly dead
c9765e50 477 kbd.onkeyevent.resetHistory();
b22c9ef9
PO
478 this.clock.tick(100);
479 expect(kbd.onkeyevent).to.not.have.been.called;
480 });
481
482 it('should generate AltGraph for quick Ctrl+Alt sequence', function () {
2b5f94fa 483 const kbd = new Keyboard(document);
b22c9ef9
PO
484 kbd.onkeyevent = sinon.spy();
485 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
486 this.clock.tick(20);
487 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));
488 expect(kbd.onkeyevent).to.have.been.calledOnce;
489 expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
490
491 // Check that the timer is properly dead
c9765e50 492 kbd.onkeyevent.resetHistory();
b22c9ef9
PO
493 this.clock.tick(100);
494 expect(kbd.onkeyevent).to.not.have.been.called;
495 });
496
497 it('should generate Ctrl, Alt for slow Ctrl+Alt sequence', function () {
2b5f94fa 498 const kbd = new Keyboard(document);
b22c9ef9
PO
499 kbd.onkeyevent = sinon.spy();
500 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
501 this.clock.tick(60);
502 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));
503 expect(kbd.onkeyevent).to.have.been.calledTwice;
504 expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
505 expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffea, "AltRight", true);
506
507 // Check that the timer is properly dead
c9765e50 508 kbd.onkeyevent.resetHistory();
b22c9ef9
PO
509 this.clock.tick(100);
510 expect(kbd.onkeyevent).to.not.have.been.called;
511 });
512
513 it('should pass through single Alt', function () {
2b5f94fa 514 const kbd = new Keyboard(document);
b22c9ef9
PO
515 kbd.onkeyevent = sinon.spy();
516 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
517 expect(kbd.onkeyevent).to.have.been.calledOnce;
518 expect(kbd.onkeyevent).to.have.been.calledWith(0xffea, 'AltRight', true);
519 });
520
521 it('should pass through single AltGr', function () {
2b5f94fa 522 const kbd = new Keyboard(document);
b22c9ef9
PO
523 kbd.onkeyevent = sinon.spy();
524 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2}));
525 expect(kbd.onkeyevent).to.have.been.calledOnce;
526 expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
f00b6fb6 527 });
528 });
ccb511a5
PO
529
530 describe('Missing Shift keyup on Windows', function () {
531 let origNavigator;
532 beforeEach(function () {
533 // window.navigator is a protected read-only property in many
534 // environments, so we need to redefine it whilst running these
535 // tests.
536 origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
ccb511a5
PO
537
538 Object.defineProperty(window, "navigator", {value: {}});
539 if (window.navigator.platform !== undefined) {
540 // Object.defineProperty() doesn't work properly in old
541 // versions of Chrome
542 this.skip();
543 }
544
545 window.navigator.platform = "Windows x86_64";
546
547 this.clock = sinon.useFakeTimers();
548 });
549 afterEach(function () {
eb05b45b
PO
550 if (origNavigator !== undefined) {
551 Object.defineProperty(window, "navigator", origNavigator);
552 }
553 if (this.clock !== undefined) {
554 this.clock.restore();
555 }
ccb511a5
PO
556 });
557
558 it('should fake a left Shift keyup', function () {
559 const kbd = new Keyboard(document);
560 kbd.onkeyevent = sinon.spy();
561
562 kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftLeft', key: 'Shift', location: 1}));
563 expect(kbd.onkeyevent).to.have.been.calledOnce;
564 expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', true);
565 kbd.onkeyevent.resetHistory();
566
567 kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftRight', key: 'Shift', location: 2}));
568 expect(kbd.onkeyevent).to.have.been.calledOnce;
569 expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', true);
570 kbd.onkeyevent.resetHistory();
571
572 kbd._handleKeyUp(keyevent('keyup', {code: 'ShiftLeft', key: 'Shift', location: 1}));
573 expect(kbd.onkeyevent).to.have.been.calledTwice;
574 expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', false);
575 expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', false);
576 });
577
578 it('should fake a right Shift keyup', function () {
579 const kbd = new Keyboard(document);
580 kbd.onkeyevent = sinon.spy();
581
582 kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftLeft', key: 'Shift', location: 1}));
583 expect(kbd.onkeyevent).to.have.been.calledOnce;
584 expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', true);
585 kbd.onkeyevent.resetHistory();
586
587 kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftRight', key: 'Shift', location: 2}));
588 expect(kbd.onkeyevent).to.have.been.calledOnce;
589 expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', true);
590 kbd.onkeyevent.resetHistory();
591
592 kbd._handleKeyUp(keyevent('keyup', {code: 'ShiftRight', key: 'Shift', location: 2}));
593 expect(kbd.onkeyevent).to.have.been.calledTwice;
594 expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', false);
595 expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', false);
596 });
597 });
f00b6fb6 598});