diff --git a/core/input/devices.js b/core/input/devices.js index 8aaabbd6..c68966b3 100644 --- a/core/input/devices.js +++ b/core/input/devices.js @@ -29,12 +29,10 @@ const Keyboard = function (defaults) { // create the keyboard handler this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), - KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */ - KeyboardUtil.TrackKeyState( - KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this)) - ) + KeyboardUtil.TrackKeyState( + KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this)) ) - ); /* jshint newcap: true */ + ); // keep these here so we can refer to them later this._eventHandlers = { diff --git a/core/input/util.js b/core/input/util.js index 508193ee..18867af8 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -196,23 +196,20 @@ export function getKeycode(evt){ // if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which export function getKeysym(evt){ var codepoint; - if (evt.char && evt.char.length === 1) { - codepoint = evt.char.charCodeAt(); - } - else if (evt.charCode) { + + if ('key' in evt) { + // Ignore special keys + if (evt.key.length === 1) { + codepoint = evt.key.charCodeAt(); + } + } else if ('charCode' in evt) { codepoint = evt.charCode; } - else if (evt.keyCode && evt.type === 'keypress') { - // IE10 stores the char code as keyCode, and has no other useful properties - codepoint = evt.keyCode; - } + if (codepoint) { return keysyms.lookup(codepoint); } - // we could check evt.key here. - // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list, - // so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key - // so we don't *need* it yet + if (evt.keyCode) { return keysymFromKeyCode(evt.keyCode, evt.shiftKey); } @@ -437,7 +434,6 @@ export function TrackQEMUKeyState (next) { // - determines a code identifying the key that was pressed (corresponding to the code/keyCode properties on the DOM event) // - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down // - marks each event with an 'escape' property if a modifier was down which should be "escaped" -// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown // This information is collected into an object which is passed to the next() function. (one call per event) export function KeyEventDecoder (modifierState, next) { "use strict"; @@ -476,10 +472,6 @@ export function KeyEventDecoder (modifierState, next) { // so only do that if we have to. var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt)); - // If a char modifier is down on a keydown, we need to insert a stall, - // so VerifyCharModifier knows to wait and see if a keypress is comnig - var stall = type === 'keydown' && modifierState.activeCharModifier() && !nonCharacterKey(evt); - // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt) var active = modifierState.activeCharModifier(); @@ -498,10 +490,6 @@ export function KeyEventDecoder (modifierState, next) { } } - if (stall) { - // insert a fake "stall" event - next({type: 'stall'}); - } next(result); return suppress; @@ -526,64 +514,6 @@ export function KeyEventDecoder (modifierState, next) { }; }; -// Combines keydown and keypress events where necessary to handle char modifiers. -// On some OS'es, a char modifier is sometimes used as a shortcut modifier. -// For example, on Windows, AltGr is synonymous with Ctrl-Alt. On a Danish keyboard layout, AltGr-2 yields a @, but Ctrl-Alt-D does nothing -// so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not. -// The only way we can distinguish these cases is to wait and see if a keypress event arrives -// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two -export function VerifyCharModifier (next) { - "use strict"; - var queue = []; - var timer = null; - function process() { - if (timer) { - return; - } - - var delayProcess = function () { - clearTimeout(timer); - timer = null; - process(); - }; - - while (queue.length !== 0) { - var cur = queue[0]; - queue = queue.splice(1); - switch (cur.type) { - case 'stall': - // insert a delay before processing available events. - /* jshint loopfunc: true */ - timer = setTimeout(delayProcess, 5); - /* jshint loopfunc: false */ - return; - case 'keydown': - // is the next element a keypress? Then we should merge the two - if (queue.length !== 0 && queue[0].type === 'keypress') { - // Firefox sends keypress even when no char is generated. - // so, if keypress keysym is the same as we'd have guessed from keydown, - // the modifier didn't have any effect, and should not be escaped - if (queue[0].escape && (!cur.keysym || cur.keysym !== queue[0].keysym)) { - cur.escape = queue[0].escape; - } - cur.keysym = queue[0].keysym; - queue = queue.splice(1); - } - break; - } - - // swallow stall events, and pass all others to the next stage - if (cur.type !== 'stall') { - next(cur); - } - } - } - return function(evt) { - queue.push(evt); - process(); - }; -}; - // Keeps track of which keys we (and the server) believe are down // When a keyup is received, match it against this list, to determine the corresponding keysym(s) // in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars diff --git a/tests/test.helper.js b/tests/test.helper.js index 3d0afb55..b66c5e57 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -99,25 +99,11 @@ describe('Helpers', function() { }); describe('getKeysym', function() { - it('should prefer char', function() { - expect(KeyboardUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61); + it('should prefer key', function() { + expect(KeyboardUtil.getKeysym({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61); }); - it('should use charCode if no char', function() { - expect(KeyboardUtil.getKeysym({char : '', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); + it('should use charCode if no key', function() { expect(KeyboardUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); - expect(KeyboardUtil.getKeysym({char : 'hello', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); - }); - it('should use keyCode if no charCode', function() { - expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: false})).to.be.equal(0x62); - expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: true})).to.be.equal(0x42); - }); - it('should return null for unknown keycodes', function() { - expect(KeyboardUtil.getKeysym({keyCode: 0xc0, which: 0xc1, shiftKey:false})).to.be.null; - expect(KeyboardUtil.getKeysym({keyCode: 0xde, which: 0xdf, shiftKey:false})).to.be.null; - }); - it('should use which if no keyCode', function() { - expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: false})).to.be.equal(0x63); - expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: true})).to.be.equal(0x43); }); describe('Non-character keys', function() { @@ -133,6 +119,10 @@ describe('Helpers', function() { expect(KeyboardUtil.getKeysym({keyCode: 0x1b})).to.be.equal(0xFF1B); expect(KeyboardUtil.getKeysym({keyCode: 0x26})).to.be.equal(0xFF52); }); + it('should return null for unknown keycodes', function() { + expect(KeyboardUtil.getKeysym({keyCode: 0xc0})).to.be.null; + expect(KeyboardUtil.getKeysym({keyCode: 0xde})).to.be.null; + }); it('should not recognize character keys', function() { expect(KeyboardUtil.getKeysym({keyCode: 'A'})).to.be.null; expect(KeyboardUtil.getKeysym({keyCode: '1'})).to.be.null; diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index aa961420..f29a3394 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -12,26 +12,26 @@ describe('Key Event Pipeline Stages', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.an.object; done(); - }).keydown({code: 'KeyA', keyCode: 0x41}); + }).keydown({code: 'KeyA', key: 'a'}); }); it('should pass the right keysym through', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt.keysym).to.be.deep.equal(0x61); done(); - }).keypress({code: 'KeyA', keyCode: 0x41}); + }).keypress({code: 'KeyA', key: 'a'}); }); it('should pass the right keyid through', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.have.property('code', 'KeyA'); done(); - }).keydown({code: 'KeyA', keyCode: 0x41}); + }).keydown({code: 'KeyA', key: 'a'}); }); it('should not sync modifiers on a keypress', function() { // Firefox provides unreliable modifier state on keypress events var count = 0; KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { ++count; - }).keypress({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); + }).keypress({code: 'KeyA', key: 'a', ctrlKey: true}); expect(count).to.be.equal(1); }); it('should sync modifiers if necessary', function(done) { @@ -47,61 +47,36 @@ describe('Key Event Pipeline Stages', function() { done(); break; } - }).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); + }).keydown({code: 'KeyA', key: 'a', ctrlKey: true}); }); it('should forward keydown events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); done(); - }).keydown({code: 'KeyA', keyCode: 0x41}); + }).keydown({code: 'KeyA', key: 'a'}); }); it('should forward keyup events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'}); done(); - }).keyup({code: 'KeyA', keyCode: 0x41}); + }).keyup({code: 'KeyA', key: 'a'}); }); it('should forward keypress events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'}); done(); - }).keypress({code: 'KeyA', keyCode: 0x41}); - }); - it('should generate stalls if a char modifier is down while a key is pressed', function(done) { - var count = 0; - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { - switch (count) { - case 0: // fake altgr - expect(evt).to.be.deep.equal({keysym: 0xfe03, type: 'keydown'}); - ++count; - break; - case 1: // stall before processing the 'a' keydown - expect(evt).to.be.deep.equal({type: 'stall'}); - ++count; - break; - case 2: // 'a' - expect(evt).to.be.deep.equal({ - type: 'keydown', - code: 'KeyA', - keysym: 0x61 - }); - - done(); - break; - } - }).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true}); - + }).keypress({code: 'KeyA', key: 'a'}); }); describe('suppress the right events at the right time', function() { it('should suppress anything while a shortcut modifier is down', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); obj.keydown({keyCode: 0x11}); // press ctrl - expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.true; + expect(obj.keydown({key: 'A'})).to.be.true; expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true; - expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.true; - expect(obj.keydown({keyCode: 0x3c})).to.be.true; // < key on DK Windows - expect(obj.keydown({keyCode: 0xde})).to.be.true; // Ø key on DK + expect(obj.keydown({key: '1'})).to.be.true; + expect(obj.keydown({key: '<'})).to.be.true; + expect(obj.keydown({key: 'ø'})).to.be.true; }); it('should suppress non-character keys', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); @@ -127,48 +102,35 @@ describe('Key Event Pipeline Stages', function() { it('should not suppress character keys', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false; + expect(obj.keydown({key: 'A'})).to.be.false; expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; - expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false; - expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows - expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK + expect(obj.keydown({key: '1'})).to.be.false; + expect(obj.keydown({key: '<'})).to.be.false; // < key on DK Windows + expect(obj.keydown({key: 'ø'})).to.be.false; // Ø key on DK }); it('should not suppress if a char modifier is down', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {}); obj.keydown({keyCode: 0xe1}); // press altgr - expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false; + expect(obj.keydown({key: 'A'})).to.be.false; expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; - expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false; - expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows - expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK + expect(obj.keydown({key: '1'})).to.be.false; + expect(obj.keydown({key: '<'})).to.be.false; + expect(obj.keydown({key: 'ø'})).to.be.false; }); }); describe('Keypress and keyup events', function() { it('should always suppress event propagation', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - expect(obj.keypress({keyCode: 'A'.charCodeAt()})).to.be.true; - expect(obj.keypress({keyCode: 0x3c})).to.be.true; // < key on DK Windows + expect(obj.keypress({key: 'A'})).to.be.true; + expect(obj.keypress({key: '<'})).to.be.true; expect(obj.keypress({keyCode: 0x11})).to.be.true; - expect(obj.keyup({keyCode: 'A'.charCodeAt()})).to.be.true; - expect(obj.keyup({keyCode: 0x3c})).to.be.true; // < key on DK Windows + expect(obj.keyup({key: 'A'})).to.be.true; + expect(obj.keyup({key: '<'})).to.be.true; expect(obj.keyup({keyCode: 0x11})).to.be.true; }); - it('should never generate stalls', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt.type).to.not.be.equal('stall'); - }); - - obj.keypress({keyCode: 'A'.charCodeAt()}); - obj.keypress({keyCode: 0x3c}); - obj.keypress({keyCode: 0x11}); - - obj.keyup({keyCode: 'A'.charCodeAt()}); - obj.keyup({keyCode: 0x3c}); - obj.keyup({keyCode: 0x11}); - }); }); describe('mark events if a char modifier is down', function() { it('should not mark modifiers on a keydown event', function() { @@ -184,7 +146,7 @@ describe('Key Event Pipeline Stages', function() { }); obj.keydown({keyCode: 0xe1}); // press altgr - obj.keydown({code: 'KeyA', keyCode: 0x41}); + obj.keydown({code: 'KeyA', key: 'a'}); }); it('should indicate on events if a single-key char modifier is down', function(done) { @@ -206,7 +168,7 @@ describe('Key Event Pipeline Stages', function() { }); obj.keydown({keyCode: 0xe1}); // press altgr - obj.keypress({code: 'KeyA', keyCode: 0x41}); + obj.keypress({code: 'KeyA', key: 'a'}); }); it('should indicate on events if a multi-key char modifier is down', function(done) { var times_called = 0; @@ -230,7 +192,7 @@ describe('Key Event Pipeline Stages', function() { obj.keydown({keyCode: 0x11}); // press ctrl obj.keydown({keyCode: 0x12}); // press alt - obj.keypress({code: 'KeyA', keyCode: 0x41}); + obj.keypress({code: 'KeyA', key: 'a'}); }); it('should not consider a char modifier to be down on the modifier key itself', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { @@ -245,7 +207,7 @@ describe('Key Event Pipeline Stages', function() { it('should remove keysym from keydown if a char key and no modifier', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); - }).keydown({code: 'KeyA', keyCode: 0x41}); + }).keydown({code: 'KeyA', key: 'a'}); }); it('should not remove keysym from keydown if a shortcut modifier is down', function() { var times_called = 0; @@ -255,19 +217,19 @@ describe('Key Event Pipeline Stages', function() { expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); break; } - }).keydown({code: 'KeyA', keyCode: 0x41, ctrlKey: true}); + }).keydown({code: 'KeyA', key: 'a', ctrlKey: true}); expect(times_called).to.be.equal(2); }); it('should not remove keysym from keydown if a char modifier is down', function() { var times_called = 0; KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { switch (times_called++) { - case 2: + case 1: expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); break; } - }).keydown({code: 'KeyA', keyCode: 0x41, altGraphKey: true}); - expect(times_called).to.be.equal(3); + }).keydown({code: 'KeyA', key: 'a', altGraphKey: true}); + expect(times_called).to.be.equal(2); }); it('should not remove keysym from keydown if key is noncharacter', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { @@ -281,131 +243,18 @@ describe('Key Event Pipeline Stages', function() { it('should never remove keysym from keypress', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'}); - }).keypress({code: 'KeyA', keyCode: 0x41}); + }).keypress({code: 'KeyA', key: 'a'}); }); it('should never remove keysym from keyup', function() { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'}); - }).keyup({code: 'KeyA', keyCode: 0x41}); + }).keyup({code: 'KeyA', key: 'a'}); }); }); // on keypress, keyup(?), always set keysym // on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down }); - describe('Verify that char modifiers are active', function() { - it('should pass keydown events through if there is no stall', function(done) { - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x41}); - done(); - })({type: 'keydown', code: 'KeyA', keysym: 0x41}); - }); - it('should pass keyup events through if there is no stall', function(done) { - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keyup', code: 'KeyA', keysym: 0x41}); - done(); - })({type: 'keyup', code: 'KeyA', keysym: 0x41}); - }); - it('should pass keypress events through if there is no stall', function(done) { - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.deep.equal({type: 'keypress', code: 'KeyA', keysym: 0x41}); - done(); - })({type: 'keypress', code: 'KeyA', keysym: 0x41}); - }); - it('should not pass stall events through', function(done){ - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - // should only be called once, for the keydown - expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x41}); - done(); - }); - - obj({type: 'stall'}); - obj({type: 'keydown', code: 'KeyA', keysym: 0x41}); - }); - it('should merge keydown and keypress events if they come after a stall', function(done) { - var next_called = false; - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - // should only be called once, for the keydown - expect(next_called).to.be.false; - next_called = true; - expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44}); - done(); - }); - - obj({type: 'stall'}); - obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); - obj({type: 'keypress', code: 'KeyC', keysym: 0x44}); - expect(next_called).to.be.false; - }); - it('should preserve modifier attribute when merging if keysyms differ', function(done) { - var next_called = false; - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - // should only be called once, for the keydown - expect(next_called).to.be.false; - next_called = true; - expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x44, escape: [0xffe3]}); - done(); - }); - - obj({type: 'stall'}); - obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); - obj({type: 'keypress', code: 'KeyC', keysym: 0x44, escape: [0xffe3]}); - expect(next_called).to.be.false; - }); - it('should not preserve modifier attribute when merging if keysyms are the same', function() { - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - expect(evt).to.not.have.property('escape'); - }); - - obj({type: 'stall'}); - obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); - obj({type: 'keypress', code: 'KeyC', keysym: 0x42, escape: [0xffe3]}); - }); - it('should not merge keydown and keypress events if there is no stall', function(done) { - var times_called = 0; - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - switch(times_called) { - case 0: - expect(evt).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42}); - break; - case 1: - expect(evt).to.deep.equal({type: 'keypress', code: 'KeyC', keysym: 0x44}); - done(); - break; - } - - ++times_called; - }); - - obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); - obj({type: 'keypress', code: 'KeyC', keysym: 0x44}); - }); - it('should not merge keydown and keypress events if separated by another event', function(done) { - var times_called = 0; - var obj = KeyboardUtil.VerifyCharModifier(function(evt){ - switch(times_called) { - case 0: - expect(evt,1).to.deep.equal({type: 'keydown', code: 'KeyA', keysym: 0x42}); - break; - case 1: - expect(evt,2).to.deep.equal({type: 'keyup', code: 'KeyC', keysym: 0x44}); - break; - case 2: - expect(evt,3).to.deep.equal({type: 'keypress', code: 'KeyE', keysym: 0x46}); - done(); - break; - } - - ++times_called; - }); - - obj({type: 'stall'}); - obj({type: 'keydown', code: 'KeyA', keysym: 0x42}); - obj({type: 'keyup', code: 'KeyC', keysym: 0x44}); - obj({type: 'keypress', code: 'KeyE', keysym: 0x46}); - }); - }); - describe('Track Key State', function() { it('should do nothing on keyup events if no keys are down', function() { var obj = KeyboardUtil.TrackKeyState(function(evt) {