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