test.keyboard.js 38 KB

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