test.keyboard.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. // requires local modules: input, keyboard, keysymdef
  2. var assert = chai.assert;
  3. var expect = chai.expect;
  4. /* jshint newcap: false, expr: true */
  5. describe('Key Event Pipeline Stages', function() {
  6. "use strict";
  7. describe('Decode Keyboard Events', function() {
  8. it('should pass events to the next stage', function(done) {
  9. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  10. expect(evt).to.be.an.object;
  11. done();
  12. }).keydown({keyCode: 0x41});
  13. });
  14. it('should pass the right keysym through', function(done) {
  15. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  16. expect(evt.keysym).to.be.deep.equal(keysyms.lookup(0x61));
  17. done();
  18. }).keypress({keyCode: 0x41});
  19. });
  20. it('should pass the right keyid through', function(done) {
  21. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  22. expect(evt).to.have.property('keyId', 0x41);
  23. done();
  24. }).keydown({keyCode: 0x41});
  25. });
  26. it('should not sync modifiers on a keypress', function() {
  27. // Firefox provides unreliable modifier state on keypress events
  28. var count = 0;
  29. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  30. ++count;
  31. }).keypress({keyCode: 0x41, ctrlKey: true});
  32. expect(count).to.be.equal(1);
  33. });
  34. it('should sync modifiers if necessary', function(done) {
  35. var count = 0;
  36. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  37. switch (count) {
  38. case 0: // fake a ctrl keydown
  39. expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xffe3), type: 'keydown'});
  40. ++count;
  41. break;
  42. case 1:
  43. expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown', keysym: keysyms.lookup(0x61)});
  44. done();
  45. break;
  46. }
  47. }).keydown({keyCode: 0x41, ctrlKey: true});
  48. });
  49. it('should forward keydown events with the right type', function(done) {
  50. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  51. expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
  52. done();
  53. }).keydown({keyCode: 0x41});
  54. });
  55. it('should forward keyup events with the right type', function(done) {
  56. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  57. expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'});
  58. done();
  59. }).keyup({keyCode: 0x41});
  60. });
  61. it('should forward keypress events with the right type', function(done) {
  62. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  63. expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'});
  64. done();
  65. }).keypress({keyCode: 0x41});
  66. });
  67. it('should generate stalls if a char modifier is down while a key is pressed', function(done) {
  68. var count = 0;
  69. KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
  70. switch (count) {
  71. case 0: // fake altgr
  72. expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xfe03), type: 'keydown'});
  73. ++count;
  74. break;
  75. case 1: // stall before processing the 'a' keydown
  76. expect(evt).to.be.deep.equal({type: 'stall'});
  77. ++count;
  78. break;
  79. case 2: // 'a'
  80. expect(evt).to.be.deep.equal({
  81. type: 'keydown',
  82. keyId: 0x41,
  83. keysym: keysyms.lookup(0x61)
  84. });
  85. done();
  86. break;
  87. }
  88. }).keydown({keyCode: 0x41, altGraphKey: true});
  89. });
  90. describe('suppress the right events at the right time', function() {
  91. it('should suppress anything while a shortcut modifier is down', function() {
  92. var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
  93. obj.keydown({keyCode: 0x11}); // press ctrl
  94. expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.true;
  95. expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true;
  96. expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.true;
  97. expect(obj.keydown({keyCode: 0x3c})).to.be.true; // < key on DK Windows
  98. expect(obj.keydown({keyCode: 0xde})).to.be.true; // Ø key on DK
  99. });
  100. it('should suppress non-character keys', function() {
  101. var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
  102. expect(obj.keydown({keyCode: 0x08}), 'a').to.be.true;
  103. expect(obj.keydown({keyCode: 0x09}), 'b').to.be.true;
  104. expect(obj.keydown({keyCode: 0x11}), 'd').to.be.true;
  105. expect(obj.keydown({keyCode: 0x12}), 'e').to.be.true;
  106. });
  107. it('should not suppress shift', function() {
  108. var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
  109. expect(obj.keydown({keyCode: 0x10}), 'd').to.be.false;
  110. });
  111. it('should generate event for shift keydown', function() {
  112. var called = false;
  113. var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  114. expect(evt).to.have.property('keysym');
  115. called = true;
  116. }).keydown({keyCode: 0x10});
  117. expect(called).to.be.true;
  118. });
  119. it('should not suppress character keys', function() {
  120. var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
  121. expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false;
  122. expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
  123. expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false;
  124. expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows
  125. expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK
  126. });
  127. it('should not suppress if a char modifier is down', function() {
  128. var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {});
  129. obj.keydown({keyCode: 0xe1}); // press altgr
  130. expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false;
  131. expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
  132. expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false;
  133. expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows
  134. expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK
  135. });
  136. });
  137. describe('Keypress and keyup events', function() {
  138. it('should always suppress event propagation', function() {
  139. var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
  140. expect(obj.keypress({keyCode: 'A'.charCodeAt()})).to.be.true;
  141. expect(obj.keypress({keyCode: 0x3c})).to.be.true; // < key on DK Windows
  142. expect(obj.keypress({keyCode: 0x11})).to.be.true;
  143. expect(obj.keyup({keyCode: 'A'.charCodeAt()})).to.be.true;
  144. expect(obj.keyup({keyCode: 0x3c})).to.be.true; // < key on DK Windows
  145. expect(obj.keyup({keyCode: 0x11})).to.be.true;
  146. });
  147. it('should never generate stalls', function() {
  148. var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  149. expect(evt.type).to.not.be.equal('stall');
  150. });
  151. obj.keypress({keyCode: 'A'.charCodeAt()});
  152. obj.keypress({keyCode: 0x3c});
  153. obj.keypress({keyCode: 0x11});
  154. obj.keyup({keyCode: 'A'.charCodeAt()});
  155. obj.keyup({keyCode: 0x3c});
  156. obj.keyup({keyCode: 0x11});
  157. });
  158. });
  159. describe('mark events if a char modifier is down', function() {
  160. it('should not mark modifiers on a keydown event', function() {
  161. var times_called = 0;
  162. var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
  163. switch (times_called++) {
  164. case 0: //altgr
  165. break;
  166. case 1: // 'a'
  167. expect(evt).to.not.have.property('escape');
  168. break;
  169. }
  170. });
  171. obj.keydown({keyCode: 0xe1}); // press altgr
  172. obj.keydown({keyCode: 'A'.charCodeAt()});
  173. });
  174. it('should indicate on events if a single-key char modifier is down', function(done) {
  175. var times_called = 0;
  176. var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
  177. switch (times_called++) {
  178. case 0: //altgr
  179. break;
  180. case 1: // 'a'
  181. expect(evt).to.be.deep.equal({
  182. type: 'keypress',
  183. keyId: 'A'.charCodeAt(),
  184. keysym: keysyms.lookup('a'.charCodeAt()),
  185. escape: [0xfe03]
  186. });
  187. done();
  188. return;
  189. }
  190. });
  191. obj.keydown({keyCode: 0xe1}); // press altgr
  192. obj.keypress({keyCode: 'A'.charCodeAt()});
  193. });
  194. it('should indicate on events if a multi-key char modifier is down', function(done) {
  195. var times_called = 0;
  196. var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xffe9, 0xffe3]), function(evt) {
  197. switch (times_called++) {
  198. case 0: //ctrl
  199. break;
  200. case 1: //alt
  201. break;
  202. case 2: // 'a'
  203. expect(evt).to.be.deep.equal({
  204. type: 'keypress',
  205. keyId: 'A'.charCodeAt(),
  206. keysym: keysyms.lookup('a'.charCodeAt()),
  207. escape: [0xffe9, 0xffe3]
  208. });
  209. done();
  210. return;
  211. }
  212. });
  213. obj.keydown({keyCode: 0x11}); // press ctrl
  214. obj.keydown({keyCode: 0x12}); // press alt
  215. obj.keypress({keyCode: 'A'.charCodeAt()});
  216. });
  217. it('should not consider a char modifier to be down on the modifier key itself', function() {
  218. var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
  219. expect(evt).to.not.have.property('escape');
  220. });
  221. obj.keydown({keyCode: 0xe1}); // press altgr
  222. });
  223. });
  224. describe('add/remove keysym', function() {
  225. it('should remove keysym from keydown if a char key and no modifier', function() {
  226. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  227. expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
  228. }).keydown({keyCode: 0x41});
  229. });
  230. it('should not remove keysym from keydown if a shortcut modifier is down', function() {
  231. var times_called = 0;
  232. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  233. switch (times_called++) {
  234. case 1:
  235. expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'});
  236. break;
  237. }
  238. }).keydown({keyCode: 0x41, ctrlKey: true});
  239. expect(times_called).to.be.equal(2);
  240. });
  241. it('should not remove keysym from keydown if a char modifier is down', function() {
  242. var times_called = 0;
  243. KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
  244. switch (times_called++) {
  245. case 2:
  246. expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'});
  247. break;
  248. }
  249. }).keydown({keyCode: 0x41, altGraphKey: true});
  250. expect(times_called).to.be.equal(3);
  251. });
  252. it('should not remove keysym from keydown if key is noncharacter', function() {
  253. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  254. expect(evt, 'bacobjpace').to.be.deep.equal({keyId: 0x09, keysym: keysyms.lookup(0xff09), type: 'keydown'});
  255. }).keydown({keyCode: 0x09});
  256. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  257. expect(evt, 'ctrl').to.be.deep.equal({keyId: 0x11, keysym: keysyms.lookup(0xffe3), type: 'keydown'});
  258. }).keydown({keyCode: 0x11});
  259. });
  260. it('should never remove keysym from keypress', function() {
  261. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  262. expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'});
  263. }).keypress({keyCode: 0x41});
  264. });
  265. it('should never remove keysym from keyup', function() {
  266. KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
  267. expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'});
  268. }).keyup({keyCode: 0x41});
  269. });
  270. });
  271. // on keypress, keyup(?), always set keysym
  272. // on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down
  273. });
  274. describe('Verify that char modifiers are active', function() {
  275. it('should pass keydown events through if there is no stall', function(done) {
  276. var obj = VerifyCharModifier(function(evt){
  277. expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
  278. done();
  279. })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
  280. });
  281. it('should pass keyup events through if there is no stall', function(done) {
  282. var obj = VerifyCharModifier(function(evt){
  283. expect(evt).to.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)});
  284. done();
  285. })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)});
  286. });
  287. it('should pass keypress events through if there is no stall', function(done) {
  288. var obj = VerifyCharModifier(function(evt){
  289. expect(evt).to.deep.equal({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)});
  290. done();
  291. })({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)});
  292. });
  293. it('should not pass stall events through', function(done){
  294. var obj = VerifyCharModifier(function(evt){
  295. // should only be called once, for the keydown
  296. expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
  297. done();
  298. });
  299. obj({type: 'stall'});
  300. obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
  301. });
  302. it('should merge keydown and keypress events if they come after a stall', function(done) {
  303. var next_called = false;
  304. var obj = VerifyCharModifier(function(evt){
  305. // should only be called once, for the keydown
  306. expect(next_called).to.be.false;
  307. next_called = true;
  308. expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44)});
  309. done();
  310. });
  311. obj({type: 'stall'});
  312. obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  313. obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
  314. expect(next_called).to.be.false;
  315. });
  316. it('should preserve modifier attribute when merging if keysyms differ', function(done) {
  317. var next_called = false;
  318. var obj = VerifyCharModifier(function(evt){
  319. // should only be called once, for the keydown
  320. expect(next_called).to.be.false;
  321. next_called = true;
  322. expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44), escape: [0xffe3]});
  323. done();
  324. });
  325. obj({type: 'stall'});
  326. obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  327. obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44), escape: [0xffe3]});
  328. expect(next_called).to.be.false;
  329. });
  330. it('should not preserve modifier attribute when merging if keysyms are the same', function() {
  331. var obj = VerifyCharModifier(function(evt){
  332. expect(evt).to.not.have.property('escape');
  333. });
  334. obj({type: 'stall'});
  335. obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  336. obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x42), escape: [0xffe3]});
  337. });
  338. it('should not merge keydown and keypress events if there is no stall', function(done) {
  339. var times_called = 0;
  340. var obj = VerifyCharModifier(function(evt){
  341. switch(times_called) {
  342. case 0:
  343. expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  344. break;
  345. case 1:
  346. expect(evt).to.deep.equal({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
  347. done();
  348. break;
  349. }
  350. ++times_called;
  351. });
  352. obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  353. obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
  354. });
  355. it('should not merge keydown and keypress events if separated by another event', function(done) {
  356. var times_called = 0;
  357. var obj = VerifyCharModifier(function(evt){
  358. switch(times_called) {
  359. case 0:
  360. expect(evt,1).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  361. break;
  362. case 1:
  363. expect(evt,2).to.deep.equal({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)});
  364. break;
  365. case 2:
  366. expect(evt,3).to.deep.equal({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)});
  367. done();
  368. break;
  369. }
  370. ++times_called;
  371. });
  372. obj({type: 'stall'});
  373. obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  374. obj({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)});
  375. obj({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)});
  376. });
  377. });
  378. describe('Track Key State', function() {
  379. it('should do nothing on keyup events if no keys are down', function() {
  380. var obj = TrackKeyState(function(evt) {
  381. expect(true).to.be.false;
  382. });
  383. obj({type: 'keyup', keyId: 0x41});
  384. });
  385. it('should insert into the queue on keydown if no keys are down', function() {
  386. var times_called = 0;
  387. var elem = null;
  388. var keysymsdown = {};
  389. var obj = TrackKeyState(function(evt) {
  390. ++times_called;
  391. if (elem.type == 'keyup') {
  392. expect(evt).to.have.property('keysym');
  393. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  394. delete keysymsdown[evt.keysym.keysym];
  395. }
  396. else {
  397. expect(evt).to.be.deep.equal(elem);
  398. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  399. }
  400. elem = null;
  401. });
  402. expect(elem).to.be.null;
  403. elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)};
  404. keysymsdown[keysyms.lookup(0x42).keysym] = true;
  405. obj(elem);
  406. expect(elem).to.be.null;
  407. elem = {type: 'keyup', keyId: 0x41};
  408. obj(elem);
  409. expect(elem).to.be.null;
  410. expect(times_called).to.be.equal(2);
  411. });
  412. it('should insert into the queue on keypress if no keys are down', function() {
  413. var times_called = 0;
  414. var elem = null;
  415. var keysymsdown = {};
  416. var obj = TrackKeyState(function(evt) {
  417. ++times_called;
  418. if (elem.type == 'keyup') {
  419. expect(evt).to.have.property('keysym');
  420. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  421. delete keysymsdown[evt.keysym.keysym];
  422. }
  423. else {
  424. expect(evt).to.be.deep.equal(elem);
  425. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  426. }
  427. elem = null;
  428. });
  429. expect(elem).to.be.null;
  430. elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
  431. keysymsdown[keysyms.lookup(0x42).keysym] = true;
  432. obj(elem);
  433. expect(elem).to.be.null;
  434. elem = {type: 'keyup', keyId: 0x41};
  435. obj(elem);
  436. expect(elem).to.be.null;
  437. expect(times_called).to.be.equal(2);
  438. });
  439. it('should add keysym to last key entry if keyId matches', function() {
  440. // this implies that a single keyup will release both keysyms
  441. var times_called = 0;
  442. var elem = null;
  443. var keysymsdown = {};
  444. var obj = TrackKeyState(function(evt) {
  445. ++times_called;
  446. if (elem.type == 'keyup') {
  447. expect(evt).to.have.property('keysym');
  448. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  449. delete keysymsdown[evt.keysym.keysym];
  450. }
  451. else {
  452. expect(evt).to.be.deep.equal(elem);
  453. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  454. elem = null;
  455. }
  456. });
  457. expect(elem).to.be.null;
  458. elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
  459. keysymsdown[keysyms.lookup(0x42).keysym] = true;
  460. obj(elem);
  461. expect(elem).to.be.null;
  462. elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)};
  463. keysymsdown[keysyms.lookup(0x43).keysym] = true;
  464. obj(elem);
  465. expect(elem).to.be.null;
  466. elem = {type: 'keyup', keyId: 0x41};
  467. obj(elem);
  468. expect(times_called).to.be.equal(4);
  469. });
  470. it('should create new key entry if keyId matches and keysym does not', function() {
  471. // this implies that a single keyup will release both keysyms
  472. var times_called = 0;
  473. var elem = null;
  474. var keysymsdown = {};
  475. var obj = TrackKeyState(function(evt) {
  476. ++times_called;
  477. if (elem.type == 'keyup') {
  478. expect(evt).to.have.property('keysym');
  479. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  480. delete keysymsdown[evt.keysym.keysym];
  481. }
  482. else {
  483. expect(evt).to.be.deep.equal(elem);
  484. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  485. elem = null;
  486. }
  487. });
  488. expect(elem).to.be.null;
  489. elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
  490. keysymsdown[keysyms.lookup(0x42).keysym] = true;
  491. obj(elem);
  492. expect(elem).to.be.null;
  493. elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)};
  494. keysymsdown[keysyms.lookup(0x43).keysym] = true;
  495. obj(elem);
  496. expect(times_called).to.be.equal(2);
  497. expect(elem).to.be.null;
  498. elem = {type: 'keyup', keyId: 0};
  499. obj(elem);
  500. expect(times_called).to.be.equal(3);
  501. elem = {type: 'keyup', keyId: 0};
  502. obj(elem);
  503. expect(times_called).to.be.equal(4);
  504. });
  505. it('should merge key entry if keyIds are zero and keysyms match', function() {
  506. // this implies that a single keyup will release both keysyms
  507. var times_called = 0;
  508. var elem = null;
  509. var keysymsdown = {};
  510. var obj = TrackKeyState(function(evt) {
  511. ++times_called;
  512. if (elem.type == 'keyup') {
  513. expect(evt).to.have.property('keysym');
  514. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  515. delete keysymsdown[evt.keysym.keysym];
  516. }
  517. else {
  518. expect(evt).to.be.deep.equal(elem);
  519. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  520. elem = null;
  521. }
  522. });
  523. expect(elem).to.be.null;
  524. elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
  525. keysymsdown[keysyms.lookup(0x42).keysym] = true;
  526. obj(elem);
  527. expect(elem).to.be.null;
  528. elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
  529. keysymsdown[keysyms.lookup(0x42).keysym] = true;
  530. obj(elem);
  531. expect(times_called).to.be.equal(2);
  532. expect(elem).to.be.null;
  533. elem = {type: 'keyup', keyId: 0};
  534. obj(elem);
  535. expect(times_called).to.be.equal(3);
  536. });
  537. it('should add keysym as separate entry if keyId does not match last event', function() {
  538. // this implies that separate keyups are required
  539. var times_called = 0;
  540. var elem = null;
  541. var keysymsdown = {};
  542. var obj = TrackKeyState(function(evt) {
  543. ++times_called;
  544. if (elem.type == 'keyup') {
  545. expect(evt).to.have.property('keysym');
  546. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  547. delete keysymsdown[evt.keysym.keysym];
  548. }
  549. else {
  550. expect(evt).to.be.deep.equal(elem);
  551. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  552. elem = null;
  553. }
  554. });
  555. expect(elem).to.be.null;
  556. elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
  557. keysymsdown[keysyms.lookup(0x42).keysym] = true;
  558. obj(elem);
  559. expect(elem).to.be.null;
  560. elem = {type: 'keypress', keyId: 0x42, keysym: keysyms.lookup(0x43)};
  561. keysymsdown[keysyms.lookup(0x43).keysym] = true;
  562. obj(elem);
  563. expect(elem).to.be.null;
  564. elem = {type: 'keyup', keyId: 0x41};
  565. obj(elem);
  566. expect(times_called).to.be.equal(4);
  567. elem = {type: 'keyup', keyId: 0x42};
  568. obj(elem);
  569. expect(times_called).to.be.equal(4);
  570. });
  571. it('should add keysym as separate entry if keyId does not match last event and first is zero', function() {
  572. // this implies that separate keyups are required
  573. var times_called = 0;
  574. var elem = null;
  575. var keysymsdown = {};
  576. var obj = TrackKeyState(function(evt) {
  577. ++times_called;
  578. if (elem.type == 'keyup') {
  579. expect(evt).to.have.property('keysym');
  580. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  581. delete keysymsdown[evt.keysym.keysym];
  582. }
  583. else {
  584. expect(evt).to.be.deep.equal(elem);
  585. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  586. elem = null;
  587. }
  588. });
  589. expect(elem).to.be.null;
  590. elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
  591. keysymsdown[keysyms.lookup(0x42).keysym] = true;
  592. obj(elem);
  593. expect(elem).to.be.null;
  594. elem = {type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x43)};
  595. keysymsdown[keysyms.lookup(0x43).keysym] = true;
  596. obj(elem);
  597. expect(elem).to.be.null;
  598. expect(times_called).to.be.equal(2);
  599. elem = {type: 'keyup', keyId: 0};
  600. obj(elem);
  601. expect(times_called).to.be.equal(3);
  602. elem = {type: 'keyup', keyId: 0x42};
  603. obj(elem);
  604. expect(times_called).to.be.equal(4);
  605. });
  606. it('should add keysym as separate entry if keyId does not match last event and second is zero', function() {
  607. // this implies that a separate keyups are required
  608. var times_called = 0;
  609. var elem = null;
  610. var keysymsdown = {};
  611. var obj = TrackKeyState(function(evt) {
  612. ++times_called;
  613. if (elem.type == 'keyup') {
  614. expect(evt).to.have.property('keysym');
  615. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  616. delete keysymsdown[evt.keysym.keysym];
  617. }
  618. else {
  619. expect(evt).to.be.deep.equal(elem);
  620. expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
  621. elem = null;
  622. }
  623. });
  624. expect(elem).to.be.null;
  625. elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)};
  626. keysymsdown[keysyms.lookup(0x42).keysym] = true;
  627. obj(elem);
  628. expect(elem).to.be.null;
  629. elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)};
  630. keysymsdown[keysyms.lookup(0x43).keysym] = true;
  631. obj(elem);
  632. expect(elem).to.be.null;
  633. elem = {type: 'keyup', keyId: 0x41};
  634. obj(elem);
  635. expect(times_called).to.be.equal(3);
  636. elem = {type: 'keyup', keyId: 0};
  637. obj(elem);
  638. expect(times_called).to.be.equal(4);
  639. });
  640. it('should pop matching key event on keyup', function() {
  641. var times_called = 0;
  642. var obj = TrackKeyState(function(evt) {
  643. switch (times_called++) {
  644. case 0:
  645. case 1:
  646. case 2:
  647. expect(evt.type).to.be.equal('keydown');
  648. break;
  649. case 3:
  650. expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x42, keysym: keysyms.lookup(0x62)});
  651. break;
  652. }
  653. });
  654. obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)});
  655. obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)});
  656. obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)});
  657. obj({type: 'keyup', keyId: 0x42});
  658. expect(times_called).to.equal(4);
  659. });
  660. it('should pop the first zero keyevent on keyup with zero keyId', function() {
  661. var times_called = 0;
  662. var obj = TrackKeyState(function(evt) {
  663. switch (times_called++) {
  664. case 0:
  665. case 1:
  666. case 2:
  667. expect(evt.type).to.be.equal('keydown');
  668. break;
  669. case 3:
  670. expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x61)});
  671. break;
  672. }
  673. });
  674. obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x61)});
  675. obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x62)});
  676. obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x63)});
  677. obj({type: 'keyup', keyId: 0x0});
  678. expect(times_called).to.equal(4);
  679. });
  680. it('should pop the last keyevents keysym if no match is found for keyId', function() {
  681. var times_called = 0;
  682. var obj = TrackKeyState(function(evt) {
  683. switch (times_called++) {
  684. case 0:
  685. case 1:
  686. case 2:
  687. expect(evt.type).to.be.equal('keydown');
  688. break;
  689. case 3:
  690. expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x44, keysym: keysyms.lookup(0x63)});
  691. break;
  692. }
  693. });
  694. obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)});
  695. obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)});
  696. obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)});
  697. obj({type: 'keyup', keyId: 0x44});
  698. expect(times_called).to.equal(4);
  699. });
  700. describe('Firefox sends keypress even when keydown is suppressed', function() {
  701. it('should discard the keypress', function() {
  702. var times_called = 0;
  703. var obj = TrackKeyState(function(evt) {
  704. expect(times_called).to.be.equal(0);
  705. ++times_called;
  706. });
  707. obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  708. expect(times_called).to.be.equal(1);
  709. obj({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)});
  710. });
  711. });
  712. describe('releaseAll', function() {
  713. it('should do nothing if no keys have been pressed', function() {
  714. var times_called = 0;
  715. var obj = TrackKeyState(function(evt) {
  716. ++times_called;
  717. });
  718. obj({type: 'releaseall'});
  719. expect(times_called).to.be.equal(0);
  720. });
  721. it('should release the keys that have been pressed', function() {
  722. var times_called = 0;
  723. var obj = TrackKeyState(function(evt) {
  724. switch (times_called++) {
  725. case 2:
  726. expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x41)});
  727. break;
  728. case 3:
  729. expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x42)});
  730. break;
  731. }
  732. });
  733. obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
  734. obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x42)});
  735. expect(times_called).to.be.equal(2);
  736. obj({type: 'releaseall'});
  737. expect(times_called).to.be.equal(4);
  738. obj({type: 'releaseall'});
  739. expect(times_called).to.be.equal(4);
  740. });
  741. });
  742. });
  743. describe('Escape Modifiers', function() {
  744. describe('Keydown', function() {
  745. it('should pass through when a char modifier is not down', function() {
  746. var times_called = 0;
  747. EscapeModifiers(function(evt) {
  748. expect(times_called).to.be.equal(0);
  749. ++times_called;
  750. expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  751. })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  752. expect(times_called).to.be.equal(1);
  753. });
  754. it('should generate fake undo/redo events when a char modifier is down', function() {
  755. var times_called = 0;
  756. EscapeModifiers(function(evt) {
  757. switch(times_called++) {
  758. case 0:
  759. expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe9)});
  760. break;
  761. case 1:
  762. expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe3)});
  763. break;
  764. case 2:
  765. expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]});
  766. break;
  767. case 3:
  768. expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe9)});
  769. break;
  770. case 4:
  771. expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe3)});
  772. break;
  773. }
  774. })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]});
  775. expect(times_called).to.be.equal(5);
  776. });
  777. });
  778. describe('Keyup', function() {
  779. it('should pass through when a char modifier is down', function() {
  780. var times_called = 0;
  781. EscapeModifiers(function(evt) {
  782. expect(times_called).to.be.equal(0);
  783. ++times_called;
  784. expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]});
  785. })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]});
  786. expect(times_called).to.be.equal(1);
  787. });
  788. it('should pass through when a char modifier is not down', function() {
  789. var times_called = 0;
  790. EscapeModifiers(function(evt) {
  791. expect(times_called).to.be.equal(0);
  792. ++times_called;
  793. expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  794. })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)});
  795. expect(times_called).to.be.equal(1);
  796. });
  797. });
  798. });
  799. });