]> git.proxmox.com Git - mirror_novnc.git/blob - tests/test.keyboard.js
Track keys using keyIdentifier
[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 });
96
97 describe('suppress the right events at the right time', function() {
98 beforeEach(function () {
99 if (isIE() || isEdge()) this.skip();
100 });
101 it('should suppress anything with a valid key', function() {
102 var kbd = new Keyboard({});
103 var evt = keyevent('keydown', {code: 'KeyA', key: 'a'});
104 kbd._handleKeyDown(evt);
105 expect(evt.preventDefault).to.have.been.called;
106 evt = keyevent('keyup', {code: 'KeyA', key: 'a'});
107 kbd._handleKeyUp(evt);
108 expect(evt.preventDefault).to.have.been.called;
109 });
110 it('should not suppress keys without key', function() {
111 var kbd = new Keyboard({});
112 var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
113 kbd._handleKeyDown(evt);
114 expect(evt.preventDefault).to.not.have.been.called;
115 });
116 it('should suppress the following keypress event', function() {
117 var kbd = new Keyboard({});
118 var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
119 kbd._handleKeyDown(evt);
120 var evt = keyevent('keypress', {code: 'KeyA', charCode: 0x41});
121 kbd._handleKeyPress(evt);
122 expect(evt.preventDefault).to.have.been.called;
123 });
124 });
125 });
126
127 describe('Track Key State', function() {
128 beforeEach(function () {
129 if (isIE() || isEdge()) this.skip();
130 });
131 it('should send release using the same keysym as the press', function(done) {
132 var kbd = new Keyboard({
133 onKeyEvent: function(keysym, code, down) {
134 expect(keysym).to.be.equal(0x61);
135 expect(code).to.be.equal('KeyA');
136 if (!down) {
137 done();
138 }
139 }});
140 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
141 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'}));
142 });
143 it('should send the same keysym for multiple presses', function() {
144 var count = 0;
145 var kbd = new Keyboard({
146 onKeyEvent: function(keysym, code, down) {
147 expect(keysym).to.be.equal(0x61);
148 expect(code).to.be.equal('KeyA');
149 expect(down).to.be.equal(true);
150 count++;
151 }});
152 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
153 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'}));
154 expect(count).to.be.equal(2);
155 });
156 it('should do nothing on keyup events if no keys are down', function() {
157 var callback = sinon.spy();
158 var kbd = new Keyboard({onKeyEvent: callback});
159 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
160 expect(callback).to.not.have.been.called;
161 });
162
163 describe('Legacy Events', function() {
164 it('should track keys using keyCode if no code', function(done) {
165 var kbd = new Keyboard({
166 onKeyEvent: function(keysym, code, down) {
167 expect(keysym).to.be.equal(0x61);
168 expect(code).to.be.equal('Platform65');
169 if (!down) {
170 done();
171 }
172 }});
173 kbd._handleKeyDown(keyevent('keydown', {keyCode: 65, key: 'a'}));
174 kbd._handleKeyUp(keyevent('keyup', {keyCode: 65, key: 'b'}));
175 });
176 it('should track keys using keyIdentifier if no code', function(done) {
177 var kbd = new Keyboard({
178 onKeyEvent: function(keysym, code, down) {
179 expect(keysym).to.be.equal(0x61);
180 expect(code).to.be.equal('Platform65');
181 if (!down) {
182 done();
183 }
184 }});
185 kbd._handleKeyDown(keyevent('keydown', {keyIdentifier: 'U+0041', key: 'a'}));
186 kbd._handleKeyUp(keyevent('keyup', {keyIdentifier: 'U+0041', key: 'b'}));
187 });
188 });
189 });
190
191 describe('Shuffle modifiers on macOS', function() {
192 var origNavigator;
193 beforeEach(function () {
194 // window.navigator is a protected read-only property in many
195 // environments, so we need to redefine it whilst running these
196 // tests.
197 origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
198 if (origNavigator === undefined) {
199 // Object.getOwnPropertyDescriptor() doesn't work
200 // properly in any version of IE
201 this.skip();
202 }
203
204 Object.defineProperty(window, "navigator", {value: {}});
205 if (window.navigator.platform !== undefined) {
206 // Object.defineProperty() doesn't work properly in old
207 // versions of Chrome
208 this.skip();
209 }
210
211 window.navigator.platform = "Mac x86_64";
212 });
213 afterEach(function () {
214 Object.defineProperty(window, "navigator", origNavigator);
215 });
216
217 it('should change Alt to AltGraph', function() {
218 var count = 0;
219 var kbd = new Keyboard({
220 onKeyEvent: function(keysym, code, down) {
221 switch (count++) {
222 case 0:
223 expect(keysym).to.be.equal(0xFF7E);
224 expect(code).to.be.equal('AltLeft');
225 break;
226 case 1:
227 expect(keysym).to.be.equal(0xFE03);
228 expect(code).to.be.equal('AltRight');
229 break;
230 }
231 }});
232 kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
233 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
234 expect(count).to.be.equal(2);
235 });
236 it('should change left Super to Alt', function(done) {
237 var kbd = new Keyboard({
238 onKeyEvent: function(keysym, code, down) {
239 expect(keysym).to.be.equal(0xFFE9);
240 expect(code).to.be.equal('MetaLeft');
241 done();
242 }});
243 kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1}));
244 });
245 it('should change right Super to left Super', function(done) {
246 var kbd = new Keyboard({
247 onKeyEvent: function(keysym, code, down) {
248 expect(keysym).to.be.equal(0xFFEB);
249 expect(code).to.be.equal('MetaRight');
250 done();
251 }});
252 kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2}));
253 });
254 });
255
256 describe('Escape AltGraph on Windows', function() {
257 var origNavigator;
258 beforeEach(function () {
259 // window.navigator is a protected read-only property in many
260 // environments, so we need to redefine it whilst running these
261 // tests.
262 origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
263 if (origNavigator === undefined) {
264 // Object.getOwnPropertyDescriptor() doesn't work
265 // properly in any version of IE
266 this.skip();
267 }
268
269 Object.defineProperty(window, "navigator", {value: {}});
270 if (window.navigator.platform !== undefined) {
271 // Object.defineProperty() doesn't work properly in old
272 // versions of Chrome
273 this.skip();
274 }
275
276 window.navigator.platform = "Windows x86_64";
277 });
278 afterEach(function () {
279 Object.defineProperty(window, "navigator", origNavigator);
280 });
281
282 it('should generate fake undo/redo events on press when AltGraph is down', function() {
283 var times_called = 0;
284 var kbd = new Keyboard({
285 onKeyEvent: function(keysym, code, down) {
286 switch(times_called++) {
287 case 0:
288 expect(keysym).to.be.equal(0xFFE3);
289 expect(code).to.be.equal('ControlLeft');
290 expect(down).to.be.equal(true);
291 break;
292 case 1:
293 expect(keysym).to.be.equal(0xFFEA);
294 expect(code).to.be.equal('AltRight');
295 expect(down).to.be.equal(true);
296 break;
297 case 2:
298 expect(keysym).to.be.equal(0xFFEA);
299 expect(code).to.be.equal('AltRight');
300 expect(down).to.be.equal(false);
301 break;
302 case 3:
303 expect(keysym).to.be.equal(0xFFE3);
304 expect(code).to.be.equal('ControlLeft');
305 expect(down).to.be.equal(false);
306 break;
307 case 4:
308 expect(keysym).to.be.equal(0x61);
309 expect(code).to.be.equal('KeyA');
310 expect(down).to.be.equal(true);
311 break;
312 case 5:
313 expect(keysym).to.be.equal(0xFFE3);
314 expect(code).to.be.equal('ControlLeft');
315 expect(down).to.be.equal(true);
316 break;
317 case 6:
318 expect(keysym).to.be.equal(0xFFEA);
319 expect(code).to.be.equal('AltRight');
320 expect(down).to.be.equal(true);
321 break;
322 }
323 }});
324 // First the modifier combo
325 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
326 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
327 // Next a normal character
328 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
329 expect(times_called).to.be.equal(7);
330 });
331 it('should no do anything on key release', function() {
332 var times_called = 0;
333 var kbd = new Keyboard({
334 onKeyEvent: function(keysym, code, down) {
335 switch(times_called++) {
336 case 7:
337 expect(keysym).to.be.equal(0x61);
338 expect(code).to.be.equal('KeyA');
339 expect(down).to.be.equal(false);
340 break;
341 }
342 }});
343 // First the modifier combo
344 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
345 kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
346 // Next a normal character
347 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
348 kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
349 expect(times_called).to.be.equal(8);
350 });
351 it('should not consider a char modifier to be down on the modifier key itself', function() {
352 var times_called = 0;
353 var kbd = new Keyboard({
354 onKeyEvent: function(keysym, code, down) {
355 switch(times_called++) {
356 case 0:
357 expect(keysym).to.be.equal(0xFFE3);
358 expect(code).to.be.equal('ControlLeft');
359 expect(down).to.be.equal(true);
360 break;
361 case 1:
362 expect(keysym).to.be.equal(0xFFE9);
363 expect(code).to.be.equal('AltLeft');
364 expect(down).to.be.equal(true);
365 break;
366 case 2:
367 expect(keysym).to.be.equal(0xFFE3);
368 expect(code).to.be.equal('ControlLeft');
369 expect(down).to.be.equal(true);
370 break;
371 }
372 }});
373 // First the modifier combo
374 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
375 kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
376 // Then one of the keys again
377 kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
378 expect(times_called).to.be.equal(3);
379 });
380 });
381 });