noVNC/tests/test.keyboard.js

355 lines
15 KiB
JavaScript

var assert = chai.assert;
var expect = chai.expect;
import { Keyboard } from '../core/input/devices.js';
import keysyms from '../core/input/keysymdef.js';
import * as KeyboardUtil from '../core/input/util.js';
function isIE() {
return navigator && !!(/trident/i).exec(navigator.userAgent);
}
function isEdge() {
return navigator && !!(/edge/i).exec(navigator.userAgent);
}
/* jshint newcap: false, expr: true */
describe('Key Event Handling', function() {
"use strict";
// The real KeyboardEvent constructor might not work everywhere we
// want to run these tests
function keyevent(typeArg, KeyboardEventInit) {
var e = { type: typeArg };
for (var key in KeyboardEventInit) {
e[key] = KeyboardEventInit[key];
}
e.stopPropagation = sinon.spy();
e.preventDefault = sinon.spy();
return e;
};
describe('Decode Keyboard Events', function() {
it('should decode keydown events', function(done) {
if (isIE() || isEdge()) this.skip();
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
expect(keysym).to.be.equal(0x61);
expect(code).to.be.equal('KeyA');
expect(down).to.be.equal(true);
done();
}});
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
});
it('should decode keyup events', function(done) {
if (isIE() || isEdge()) this.skip();
var calls = 0;
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
expect(keysym).to.be.equal(0x61);
expect(code).to.be.equal('KeyA');
if (calls++ === 1) {
expect(down).to.be.equal(false);
done();
}
}});
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
});
describe('Legacy keypress Events', function() {
it('should wait for keypress when needed', function() {
var callback = sinon.spy();
var kbd = new Keyboard({onKeyEvent: callback});
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
expect(callback).to.not.have.been.called;
});
it('should decode keypress events', function(done) {
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
expect(keysym).to.be.equal(0x61);
expect(code).to.be.equal('KeyA');
expect(down).to.be.equal(true);
done();
}});
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61}));
});
it('should ignore keypress with different code', function() {
var callback = sinon.spy();
var kbd = new Keyboard({onKeyEvent: callback});
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61}));
expect(callback).to.not.have.been.called;
});
it('should handle keypress with missing code', function(done) {
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
expect(keysym).to.be.equal(0x61);
expect(code).to.be.equal('KeyA');
expect(down).to.be.equal(true);
done();
}});
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61}));
});
});
describe('suppress the right events at the right time', function() {
beforeEach(function () {
if (isIE() || isEdge()) this.skip();
});
it('should suppress anything with a valid key', function() {
var kbd = new Keyboard({});
var evt = keyevent('keydown', {code: 'KeyA', key: 'a'});
kbd._handleKeyDown(evt);
expect(evt.preventDefault).to.have.been.called;
evt = keyevent('keyup', {code: 'KeyA', key: 'a'});
kbd._handleKeyUp(evt);
expect(evt.preventDefault).to.have.been.called;
});
it('should not suppress keys without key', function() {
var kbd = new Keyboard({});
var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
kbd._handleKeyDown(evt);
expect(evt.preventDefault).to.not.have.been.called;
});
it('should suppress the following keypress event', function() {
var kbd = new Keyboard({});
var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
kbd._handleKeyDown(evt);
var evt = keyevent('keypress', {code: 'KeyA', charCode: 0x41});
kbd._handleKeyPress(evt);
expect(evt.preventDefault).to.have.been.called;
});
});
});
describe('Track Key State', function() {
beforeEach(function () {
if (isIE() || isEdge()) this.skip();
});
it('should send release using the same keysym as the press', function(done) {
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
expect(keysym).to.be.equal(0x61);
expect(code).to.be.equal('KeyA');
if (!down) {
done();
}
}});
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'}));
});
it('should send the same keysym for multiple presses', function() {
var count = 0;
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
expect(keysym).to.be.equal(0x61);
expect(code).to.be.equal('KeyA');
expect(down).to.be.equal(true);
count++;
}});
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'}));
expect(count).to.be.equal(2);
});
it('should do nothing on keyup events if no keys are down', function() {
var callback = sinon.spy();
var kbd = new Keyboard({onKeyEvent: callback});
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
expect(callback).to.not.have.been.called;
});
});
describe('Shuffle modifiers on macOS', function() {
var origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.platform = "Mac x86_64";
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
it('should change Alt to AltGraph', function() {
var count = 0;
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
switch (count++) {
case 0:
expect(keysym).to.be.equal(0xFF7E);
expect(code).to.be.equal('AltLeft');
break;
case 1:
expect(keysym).to.be.equal(0xFE03);
expect(code).to.be.equal('AltRight');
break;
}
}});
kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
expect(count).to.be.equal(2);
});
it('should change left Super to Alt', function(done) {
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
expect(keysym).to.be.equal(0xFFE9);
expect(code).to.be.equal('MetaLeft');
done();
}});
kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1}));
});
it('should change right Super to left Super', function(done) {
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
expect(keysym).to.be.equal(0xFFEB);
expect(code).to.be.equal('MetaRight');
done();
}});
kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2}));
});
});
describe('Escape AltGraph on Windows', function() {
var origNavigator;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
if (origNavigator === undefined) {
// Object.getOwnPropertyDescriptor() doesn't work
// properly in any version of IE
this.skip();
}
Object.defineProperty(window, "navigator", {value: {}});
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
}
window.navigator.platform = "Windows x86_64";
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
});
it('should generate fake undo/redo events on press when AltGraph is down', function() {
var times_called = 0;
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
switch(times_called++) {
case 0:
expect(keysym).to.be.equal(0xFFE3);
expect(code).to.be.equal('ControlLeft');
expect(down).to.be.equal(true);
break;
case 1:
expect(keysym).to.be.equal(0xFFEA);
expect(code).to.be.equal('AltRight');
expect(down).to.be.equal(true);
break;
case 2:
expect(keysym).to.be.equal(0xFFEA);
expect(code).to.be.equal('AltRight');
expect(down).to.be.equal(false);
break;
case 3:
expect(keysym).to.be.equal(0xFFE3);
expect(code).to.be.equal('ControlLeft');
expect(down).to.be.equal(false);
break;
case 4:
expect(keysym).to.be.equal(0x61);
expect(code).to.be.equal('KeyA');
expect(down).to.be.equal(true);
break;
case 5:
expect(keysym).to.be.equal(0xFFE3);
expect(code).to.be.equal('ControlLeft');
expect(down).to.be.equal(true);
break;
case 6:
expect(keysym).to.be.equal(0xFFEA);
expect(code).to.be.equal('AltRight');
expect(down).to.be.equal(true);
break;
}
}});
// First the modifier combo
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
// Next a normal character
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
expect(times_called).to.be.equal(7);
});
it('should no do anything on key release', function() {
var times_called = 0;
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
switch(times_called++) {
case 7:
expect(keysym).to.be.equal(0x61);
expect(code).to.be.equal('KeyA');
expect(down).to.be.equal(false);
break;
}
}});
// First the modifier combo
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
// Next a normal character
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
expect(times_called).to.be.equal(8);
});
it('should not consider a char modifier to be down on the modifier key itself', function() {
var times_called = 0;
var kbd = new Keyboard({
onKeyEvent: function(keysym, code, down) {
switch(times_called++) {
case 0:
expect(keysym).to.be.equal(0xFFE3);
expect(code).to.be.equal('ControlLeft');
expect(down).to.be.equal(true);
break;
case 1:
expect(keysym).to.be.equal(0xFFE9);
expect(code).to.be.equal('AltLeft');
expect(down).to.be.equal(true);
break;
case 2:
expect(keysym).to.be.equal(0xFFE3);
expect(code).to.be.equal('ControlLeft');
expect(down).to.be.equal(true);
break;
}
}});
// First the modifier combo
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
// Then one of the keys again
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
expect(times_called).to.be.equal(3);
});
});
});