| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521 |
- const expect = chai.expect;
- import Keyboard from '../core/input/keyboard.js';
- 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) {
- const e = { type: typeArg };
- for (let 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) {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = (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) {
- let calls = 0;
- const kbd = new Keyboard(document);
- kbd.onkeyevent = (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('Fake keyup', function () {
- it('should fake keyup events for virtual keyboards', function (done) {
- let count = 0;
- const kbd = new Keyboard(document);
- kbd.onkeyevent = (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('Track Key State', function () {
- it('should send release using the same keysym as the press', function (done) {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = (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 () {
- let count = 0;
- const kbd = new Keyboard(document);
- kbd.onkeyevent = (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 () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
- expect(kbd.onkeyevent).to.not.have.been.called;
- });
- describe('Legacy Events', function () {
- it('should track keys using keyCode if no code', function (done) {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = (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 () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = (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) {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = (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('Shuffle modifiers on macOS', function () {
- let 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");
- 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 () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
- });
- it('should change Alt to AltGraph', function () {
- let count = 0;
- const kbd = new Keyboard(document);
- kbd.onkeyevent = (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) {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = (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) {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = (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('Caps Lock on iOS and macOS', function () {
- let 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");
- Object.defineProperty(window, "navigator", {value: {}});
- if (window.navigator.platform !== undefined) {
- // Object.defineProperty() doesn't work properly in old
- // versions of Chrome
- this.skip();
- }
- });
- afterEach(function () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
- });
- it('should toggle caps lock on key press on iOS', function () {
- window.navigator.platform = "iPad";
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));
- expect(kbd.onkeyevent).to.have.been.calledTwice;
- expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
- expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
- });
- it('should toggle caps lock on key press on mac', function () {
- window.navigator.platform = "Mac";
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));
- expect(kbd.onkeyevent).to.have.been.calledTwice;
- expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
- expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
- });
- it('should toggle caps lock on key release on iOS', function () {
- window.navigator.platform = "iPad";
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));
- expect(kbd.onkeyevent).to.have.been.calledTwice;
- expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
- expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
- });
- it('should toggle caps lock on key release on mac', function () {
- window.navigator.platform = "Mac";
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));
- expect(kbd.onkeyevent).to.have.been.calledTwice;
- expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
- expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
- });
- });
- describe('Japanese IM keys on Windows', function () {
- let 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");
- 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";
- });
- afterEach(function () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
- });
- const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,
- 'Alphanumeric': 0xff30, 'Katakana': 0xff26,
- 'Hiragana': 0xff25, 'Romaji': 0xff24,
- 'KanaMode': 0xff24 };
- for (let [key, keysym] of Object.entries(keys)) {
- it(`should fake key release for ${key} on Windows`, function () {
- let kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'FakeIM', key: key}));
- expect(kbd.onkeyevent).to.have.been.calledTwice;
- expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(keysym, "FakeIM", true);
- expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(keysym, "FakeIM", false);
- });
- }
- });
- describe('Escape AltGraph on Windows', function () {
- let 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");
- 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";
- this.clock = sinon.useFakeTimers();
- });
- afterEach(function () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
- if (this.clock !== undefined) {
- this.clock.restore();
- }
- });
- it('should supress ControlLeft until it knows if it is AltGr', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
- expect(kbd.onkeyevent).to.not.have.been.called;
- });
- it('should not trigger on repeating ControlLeft', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
- kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
- expect(kbd.onkeyevent).to.have.been.calledTwice;
- expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
- expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
- });
- it('should not supress ControlRight', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'ControlRight', key: 'Control', location: 2}));
- expect(kbd.onkeyevent).to.have.been.calledOnce;
- expect(kbd.onkeyevent).to.have.been.calledWith(0xffe4, "ControlRight", true);
- });
- it('should release ControlLeft after 100 ms', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
- expect(kbd.onkeyevent).to.not.have.been.called;
- this.clock.tick(100);
- expect(kbd.onkeyevent).to.have.been.calledOnce;
- expect(kbd.onkeyevent).to.have.been.calledWith(0xffe3, "ControlLeft", true);
- });
- it('should release ControlLeft on other key press', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
- expect(kbd.onkeyevent).to.not.have.been.called;
- kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
- expect(kbd.onkeyevent).to.have.been.calledTwice;
- expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
- expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0x61, "KeyA", true);
- // Check that the timer is properly dead
- kbd.onkeyevent.resetHistory();
- this.clock.tick(100);
- expect(kbd.onkeyevent).to.not.have.been.called;
- });
- it('should release ControlLeft on other key release', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
- kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
- expect(kbd.onkeyevent).to.have.been.calledOnce;
- expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x61, "KeyA", true);
- kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
- expect(kbd.onkeyevent).to.have.been.calledThrice;
- expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
- expect(kbd.onkeyevent.thirdCall).to.have.been.calledWith(0x61, "KeyA", false);
- // Check that the timer is properly dead
- kbd.onkeyevent.resetHistory();
- this.clock.tick(100);
- expect(kbd.onkeyevent).to.not.have.been.called;
- });
- it('should generate AltGraph for quick Ctrl+Alt sequence', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
- this.clock.tick(20);
- kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));
- expect(kbd.onkeyevent).to.have.been.calledOnce;
- expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
- // Check that the timer is properly dead
- kbd.onkeyevent.resetHistory();
- this.clock.tick(100);
- expect(kbd.onkeyevent).to.not.have.been.called;
- });
- it('should generate Ctrl, Alt for slow Ctrl+Alt sequence', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
- this.clock.tick(60);
- kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));
- expect(kbd.onkeyevent).to.have.been.calledTwice;
- expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
- expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffea, "AltRight", true);
- // Check that the timer is properly dead
- kbd.onkeyevent.resetHistory();
- this.clock.tick(100);
- expect(kbd.onkeyevent).to.not.have.been.called;
- });
- it('should pass through single Alt', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
- expect(kbd.onkeyevent).to.have.been.calledOnce;
- expect(kbd.onkeyevent).to.have.been.calledWith(0xffea, 'AltRight', true);
- });
- it('should pass through single AltGr', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2}));
- expect(kbd.onkeyevent).to.have.been.calledOnce;
- expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
- });
- });
- describe('Missing Shift keyup on Windows', function () {
- let 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");
- 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";
- this.clock = sinon.useFakeTimers();
- });
- afterEach(function () {
- if (origNavigator !== undefined) {
- Object.defineProperty(window, "navigator", origNavigator);
- }
- if (this.clock !== undefined) {
- this.clock.restore();
- }
- });
- it('should fake a left Shift keyup', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftLeft', key: 'Shift', location: 1}));
- expect(kbd.onkeyevent).to.have.been.calledOnce;
- expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', true);
- kbd.onkeyevent.resetHistory();
- kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftRight', key: 'Shift', location: 2}));
- expect(kbd.onkeyevent).to.have.been.calledOnce;
- expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', true);
- kbd.onkeyevent.resetHistory();
- kbd._handleKeyUp(keyevent('keyup', {code: 'ShiftLeft', key: 'Shift', location: 1}));
- expect(kbd.onkeyevent).to.have.been.calledTwice;
- expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', false);
- expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', false);
- });
- it('should fake a right Shift keyup', function () {
- const kbd = new Keyboard(document);
- kbd.onkeyevent = sinon.spy();
- kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftLeft', key: 'Shift', location: 1}));
- expect(kbd.onkeyevent).to.have.been.calledOnce;
- expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', true);
- kbd.onkeyevent.resetHistory();
- kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftRight', key: 'Shift', location: 2}));
- expect(kbd.onkeyevent).to.have.been.calledOnce;
- expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', true);
- kbd.onkeyevent.resetHistory();
- kbd._handleKeyUp(keyevent('keyup', {code: 'ShiftRight', key: 'Shift', location: 2}));
- expect(kbd.onkeyevent).to.have.been.calledTwice;
- expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', false);
- expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', false);
- });
- });
- });
|