]> git.proxmox.com Git - mirror_novnc.git/blobdiff - tests/test.keyboard.js
Move localization.js to app
[mirror_novnc.git] / tests / test.keyboard.js
index e4ee503f47235008034018c537645a409fb5051c..513d797f99ab1d2119d2be923b15e81d287bbdc2 100644 (file)
@@ -1,9 +1,16 @@
 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';
+import sinon from '../vendor/sinon.js';
+
+import Keyboard from '../core/input/keyboard.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() {
@@ -23,53 +30,116 @@ describe('Key Event Handling', function() {
 
     describe('Decode Keyboard Events', function() {
         it('should decode keydown events', function(done) {
-            var kbd = new Keyboard({
-            onKeyEvent: function(keysym, code, down) {
+            if (isIE() || isEdge()) this.skip();
+            var kbd = new Keyboard(document);
+            kbd.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) {
+            var kbd = new Keyboard(document);
+            kbd.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});
+                var kbd = new Keyboard(document);
+                kbd.onkeyevent = sinon.spy();
                 kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
-                expect(callback).to.not.have.been.called;
+                expect(kbd.onkeyevent).to.not.have.been.called;
             });
             it('should decode keypress events', function(done) {
-                var kbd = new Keyboard({
-                onKeyEvent: function(keysym, code, down) {
+                var kbd = new Keyboard(document);
+                kbd.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 kbd = new Keyboard(document);
+                kbd.onkeyevent = sinon.spy();
+                kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
+                kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61}));
+                expect(kbd.onkeyevent).to.not.have.been.called;
+            });
+            it('should handle keypress with missing code', function(done) {
+                var kbd = new Keyboard(document);
+                kbd.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}));
+            });
+            it('should guess key if no keypress and numeric key', function(done) {
+                var kbd = new Keyboard(document);
+                kbd.onkeyevent = function(keysym, code, down) {
+                    expect(keysym).to.be.equal(0x32);
+                    expect(code).to.be.equal('Digit2');
+                    expect(down).to.be.equal(true);
+                    done();
+                };
+                kbd._handleKeyDown(keyevent('keydown', {code: 'Digit2', keyCode: 0x32}));
+            });
+            it('should guess key if no keypress and alpha key', function(done) {
+                var kbd = new Keyboard(document);
+                kbd.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, shiftKey: false}));
+            });
+            it('should guess key if no keypress and alpha key (with shift)', function(done) {
+                var kbd = new Keyboard(document);
+                kbd.onkeyevent = function(keysym, code, down) {
+                    expect(keysym).to.be.equal(0x41);
+                    expect(code).to.be.equal('KeyA');
+                    expect(down).to.be.equal(true);
+                    done();
+                };
+                kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: true}));
+            });
+            it('should not guess key if no keypress and unknown key', function(done) {
+                var kbd = new Keyboard(document);
+                kbd.onkeyevent = function(keysym, code, down) {
+                    expect(keysym).to.be.equal(0);
+                    expect(code).to.be.equal('KeyA');
+                    expect(down).to.be.equal(true);
+                    done();
+                };
+                kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x09}));
+            });
         });
 
         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 kbd = new Keyboard(document, {});
                 var evt = keyevent('keydown', {code: 'KeyA', key: 'a'});
                 kbd._handleKeyDown(evt);
                 expect(evt.preventDefault).to.have.been.called;
@@ -78,13 +148,13 @@ describe('Key Event Handling', function() {
                 expect(evt.preventDefault).to.have.been.called;
             });
             it('should not suppress keys without key', function() {
-                var kbd = new Keyboard({});
+                var kbd = new Keyboard(document, {});
                 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 kbd = new Keyboard(document, {});
                 var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
                 kbd._handleKeyDown(evt);
                 var evt = keyevent('keypress', {code: 'KeyA', charCode: 0x41});
@@ -94,36 +164,215 @@ describe('Key Event Handling', function() {
         });
     });
 
+    describe('Fake keyup', function() {
+        it('should fake keyup events for virtual keyboards', function(done) {
+            if (isIE() || isEdge()) this.skip();
+            var count = 0;
+            var kbd = new Keyboard(document);
+            kbd.onkeyevent = function(keysym, code, down) {
+                switch (count++) {
+                    case 0:
+                        expect(keysym).to.be.equal(0x61);
+                        expect(code).to.be.equal('Unidentified');
+                        expect(down).to.be.equal(true);
+                        break;
+                    case 1:
+                        expect(keysym).to.be.equal(0x61);
+                        expect(code).to.be.equal('Unidentified');
+                        expect(down).to.be.equal(false);
+                        done();
+                }
+            };
+            kbd._handleKeyDown(keyevent('keydown', {code: 'Unidentified', key: 'a'}));
+        });
+
+        describe('iOS', 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 = "iPhone 9.0";
+            });
+            afterEach(function () {
+                Object.defineProperty(window, "navigator", origNavigator);
+            });
+
+            it('should fake keyup events on iOS', function(done) {
+                if (isIE() || isEdge()) this.skip();
+                var count = 0;
+                var kbd = new Keyboard(document);
+                kbd.onkeyevent = function(keysym, code, down) {
+                    switch (count++) {
+                        case 0:
+                            expect(keysym).to.be.equal(0x61);
+                            expect(code).to.be.equal('KeyA');
+                            expect(down).to.be.equal(true);
+                            break;
+                        case 1:
+                            expect(keysym).to.be.equal(0x61);
+                            expect(code).to.be.equal('KeyA');
+                            expect(down).to.be.equal(false);
+                            done();
+                    }
+                };
+                kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+            });
+        });
+    });
+
     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) {
+            var kbd = new Keyboard(document);
+            kbd.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(document);
+            kbd.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});
+            var kbd = new Keyboard(document);
+            kbd.onkeyevent = sinon.spy();
             kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
-            expect(callback).to.not.have.been.called;
+            expect(kbd.onkeyevent).to.not.have.been.called;
         });
-        it('should send a key release for each key press with the same code', function() {
-            var callback = sinon.spy();
-            var kbd = new Keyboard({onKeyEvent: callback});
-            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
-            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'}));
-            kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA'}));
-            expect(callback.callCount).to.be.equal(4);
+
+        describe('Legacy Events', function() {
+            it('should track keys using keyCode if no code', function(done) {
+                var kbd = new Keyboard(document);
+                kbd.onkeyevent = function(keysym, code, down) {
+                    expect(keysym).to.be.equal(0x61);
+                    expect(code).to.be.equal('Platform65');
+                    if (!down) {
+                        done();
+                    }
+                };
+                kbd._handleKeyDown(keyevent('keydown', {keyCode: 65, key: 'a'}));
+                kbd._handleKeyUp(keyevent('keyup', {keyCode: 65, key: 'b'}));
+            });
+            it('should ignore compositing code', function() {
+                var kbd = new Keyboard(document);
+                kbd.onkeyevent = function(keysym, code, down) {
+                    expect(keysym).to.be.equal(0x61);
+                    expect(code).to.be.equal('Unidentified');
+                };
+                kbd._handleKeyDown(keyevent('keydown', {keyCode: 229, key: 'a'}));
+            });
+            it('should track keys using keyIdentifier if no code', function(done) {
+                var kbd = new Keyboard(document);
+                kbd.onkeyevent = function(keysym, code, down) {
+                    expect(keysym).to.be.equal(0x61);
+                    expect(code).to.be.equal('Platform65');
+                    if (!down) {
+                        done();
+                    }
+                };
+                kbd._handleKeyDown(keyevent('keydown', {keyIdentifier: 'U+0041', key: 'a'}));
+                kbd._handleKeyUp(keyevent('keyup', {keyIdentifier: 'U+0041', key: 'b'}));
+            });
         });
     });
 
-    describe('Escape Modifiers', function() {
+    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(document);
+            kbd.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(document);
+            kbd.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(document);
+            kbd.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
@@ -149,10 +398,10 @@ describe('Key Event Handling', function() {
             Object.defineProperty(window, "navigator", origNavigator);
         });
 
-        it('should generate fake undo/redo events on press when a char modifier is down', function() {
+        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) {
+            var kbd = new Keyboard(document);
+            kbd.onkeyevent = function(keysym, code, down) {
                 switch(times_called++) {
                 case 0:
                     expect(keysym).to.be.equal(0xFFE3);
@@ -160,18 +409,18 @@ describe('Key Event Handling', function() {
                     expect(down).to.be.equal(true);
                     break;
                 case 1:
-                    expect(keysym).to.be.equal(0xFFE9);
-                    expect(code).to.be.equal('AltLeft');
+                    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(0xFFE9);
-                    expect(code).to.be.equal('Unidentified');
+                    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('Unidentified');
+                    expect(code).to.be.equal('ControlLeft');
                     expect(down).to.be.equal(false);
                     break;
                 case 4:
@@ -180,28 +429,28 @@ describe('Key Event Handling', function() {
                     expect(down).to.be.equal(true);
                     break;
                 case 5:
-                    expect(keysym).to.be.equal(0xFFE9);
-                    expect(code).to.be.equal('Unidentified');
+                    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(0xFFE3);
-                    expect(code).to.be.equal('Unidentified');
+                    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'}));
-            kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'}));
+            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) {
+            var kbd = new Keyboard(document);
+            kbd.onkeyevent = function(keysym, code, down) {
                 switch(times_called++) {
                 case 7:
                     expect(keysym).to.be.equal(0x61);
@@ -209,10 +458,10 @@ describe('Key Event Handling', function() {
                     expect(down).to.be.equal(false);
                     break;
                 }
-            }});
+            };
             // First the modifier combo
-            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'}));
-            kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'}));
+            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'}));
@@ -220,8 +469,8 @@ describe('Key Event Handling', function() {
         });
         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) {
+            var kbd = new Keyboard(document);
+            kbd.onkeyevent = function(keysym, code, down) {
                 switch(times_called++) {
                 case 0:
                     expect(keysym).to.be.equal(0xFFE3);
@@ -239,12 +488,12 @@ describe('Key Event Handling', function() {
                     expect(down).to.be.equal(true);
                     break;
                 }
-            }});
+            };
             // First the modifier combo
-            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'}));
-            kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'}));
+            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'}));
+            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
             expect(times_called).to.be.equal(3);
         });
     });