]>
Commit | Line | Data |
---|---|---|
f00b6fb6 | 1 | var assert = chai.assert; |
2 | var expect = chai.expect; | |
3 | ||
f7363fd2 | 4 | import { Keyboard } from '../core/input/devices.js'; |
dfae3209 SR |
5 | import keysyms from '../core/input/keysymdef.js'; |
6 | import * as KeyboardUtil from '../core/input/util.js'; | |
7 | ||
099eb856 PO |
8 | function isIE() { |
9 | return navigator && !!(/trident/i).exec(navigator.userAgent); | |
10 | } | |
11 | function isEdge() { | |
12 | return navigator && !!(/edge/i).exec(navigator.userAgent); | |
13 | } | |
14 | ||
31f169e8 | 15 | /* jshint newcap: false, expr: true */ |
f7363fd2 | 16 | describe('Key Event Handling', function() { |
f00b6fb6 | 17 | "use strict"; |
f00b6fb6 | 18 | |
f7363fd2 PO |
19 | // The real KeyboardEvent constructor might not work everywhere we |
20 | // want to run these tests | |
21 | function keyevent(typeArg, KeyboardEventInit) { | |
22 | var e = { type: typeArg }; | |
23 | for (var key in KeyboardEventInit) { | |
24 | e[key] = KeyboardEventInit[key]; | |
25 | } | |
26 | e.stopPropagation = sinon.spy(); | |
27 | e.preventDefault = sinon.spy(); | |
28 | return e; | |
29 | }; | |
f00b6fb6 | 30 | |
f7363fd2 PO |
31 | describe('Decode Keyboard Events', function() { |
32 | it('should decode keydown events', function(done) { | |
099eb856 | 33 | if (isIE() || isEdge()) this.skip(); |
f7363fd2 PO |
34 | var kbd = new Keyboard({ |
35 | onKeyEvent: function(keysym, code, down) { | |
36 | expect(keysym).to.be.equal(0x61); | |
37 | expect(code).to.be.equal('KeyA'); | |
38 | expect(down).to.be.equal(true); | |
39 | done(); | |
40 | }}); | |
41 | kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); | |
42 | }); | |
43 | it('should decode keyup events', function(done) { | |
099eb856 | 44 | if (isIE() || isEdge()) this.skip(); |
f7363fd2 PO |
45 | var calls = 0; |
46 | var kbd = new Keyboard({ | |
47 | onKeyEvent: function(keysym, code, down) { | |
48 | expect(keysym).to.be.equal(0x61); | |
49 | expect(code).to.be.equal('KeyA'); | |
50 | if (calls++ === 1) { | |
51 | expect(down).to.be.equal(false); | |
52 | done(); | |
53 | } | |
54 | }}); | |
55 | kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); | |
56 | kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); | |
57 | }); | |
58 | ||
59 | describe('Legacy keypress Events', function() { | |
60 | it('should wait for keypress when needed', function() { | |
61 | var callback = sinon.spy(); | |
62 | var kbd = new Keyboard({onKeyEvent: callback}); | |
63 | kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); | |
64 | expect(callback).to.not.have.been.called; | |
65 | }); | |
66 | it('should decode keypress events', function(done) { | |
67 | var kbd = new Keyboard({ | |
68 | onKeyEvent: function(keysym, code, down) { | |
69 | expect(keysym).to.be.equal(0x61); | |
70 | expect(code).to.be.equal('KeyA'); | |
71 | expect(down).to.be.equal(true); | |
72 | done(); | |
73 | }}); | |
74 | kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); | |
75 | kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61})); | |
f00b6fb6 | 76 | }); |
9fce233d PO |
77 | it('should ignore keypress with different code', function() { |
78 | var callback = sinon.spy(); | |
79 | var kbd = new Keyboard({onKeyEvent: callback}); | |
80 | kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); | |
81 | kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61})); | |
82 | expect(callback).to.not.have.been.called; | |
83 | }); | |
84 | it('should handle keypress with missing code', function(done) { | |
85 | var kbd = new Keyboard({ | |
86 | onKeyEvent: function(keysym, code, down) { | |
87 | expect(keysym).to.be.equal(0x61); | |
88 | expect(code).to.be.equal('KeyA'); | |
89 | expect(down).to.be.equal(true); | |
90 | done(); | |
91 | }}); | |
92 | kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); | |
93 | kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61})); | |
94 | }); | |
f00b6fb6 | 95 | }); |
f00b6fb6 | 96 | |
f7363fd2 | 97 | describe('suppress the right events at the right time', function() { |
099eb856 PO |
98 | beforeEach(function () { |
99 | if (isIE() || isEdge()) this.skip(); | |
100 | }); | |
f7363fd2 PO |
101 | it('should suppress anything with a valid key', function() { |
102 | var kbd = new Keyboard({}); | |
103 | var evt = keyevent('keydown', {code: 'KeyA', key: 'a'}); | |
104 | kbd._handleKeyDown(evt); | |
105 | expect(evt.preventDefault).to.have.been.called; | |
106 | evt = keyevent('keyup', {code: 'KeyA', key: 'a'}); | |
107 | kbd._handleKeyUp(evt); | |
108 | expect(evt.preventDefault).to.have.been.called; | |
109 | }); | |
110 | it('should not suppress keys without key', function() { | |
111 | var kbd = new Keyboard({}); | |
112 | var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41}); | |
113 | kbd._handleKeyDown(evt); | |
114 | expect(evt.preventDefault).to.not.have.been.called; | |
115 | }); | |
116 | it('should suppress the following keypress event', function() { | |
117 | var kbd = new Keyboard({}); | |
118 | var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41}); | |
119 | kbd._handleKeyDown(evt); | |
120 | var evt = keyevent('keypress', {code: 'KeyA', charCode: 0x41}); | |
121 | kbd._handleKeyPress(evt); | |
122 | expect(evt.preventDefault).to.have.been.called; | |
f00b6fb6 | 123 | }); |
124 | }); | |
f00b6fb6 | 125 | }); |
126 | ||
f00b6fb6 | 127 | describe('Track Key State', function() { |
099eb856 PO |
128 | beforeEach(function () { |
129 | if (isIE() || isEdge()) this.skip(); | |
130 | }); | |
f7363fd2 PO |
131 | it('should send release using the same keysym as the press', function(done) { |
132 | var kbd = new Keyboard({ | |
133 | onKeyEvent: function(keysym, code, down) { | |
134 | expect(keysym).to.be.equal(0x61); | |
135 | expect(code).to.be.equal('KeyA'); | |
136 | if (!down) { | |
137 | done(); | |
138 | } | |
139 | }}); | |
140 | kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); | |
141 | kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'})); | |
f00b6fb6 | 142 | }); |
ae820533 PO |
143 | it('should send the same keysym for multiple presses', function() { |
144 | var count = 0; | |
145 | var kbd = new Keyboard({ | |
146 | onKeyEvent: function(keysym, code, down) { | |
147 | expect(keysym).to.be.equal(0x61); | |
148 | expect(code).to.be.equal('KeyA'); | |
149 | expect(down).to.be.equal(true); | |
150 | count++; | |
151 | }}); | |
152 | kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); | |
153 | kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'})); | |
154 | expect(count).to.be.equal(2); | |
155 | }); | |
f7363fd2 PO |
156 | it('should do nothing on keyup events if no keys are down', function() { |
157 | var callback = sinon.spy(); | |
158 | var kbd = new Keyboard({onKeyEvent: callback}); | |
159 | kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); | |
160 | expect(callback).to.not.have.been.called; | |
161 | }); | |
f7363fd2 | 162 | }); |
f00b6fb6 | 163 | |
bf43c263 PO |
164 | describe('Shuffle modifiers on macOS', function() { |
165 | var origNavigator; | |
166 | beforeEach(function () { | |
167 | // window.navigator is a protected read-only property in many | |
168 | // environments, so we need to redefine it whilst running these | |
169 | // tests. | |
170 | origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); | |
171 | if (origNavigator === undefined) { | |
172 | // Object.getOwnPropertyDescriptor() doesn't work | |
173 | // properly in any version of IE | |
174 | this.skip(); | |
175 | } | |
176 | ||
177 | Object.defineProperty(window, "navigator", {value: {}}); | |
178 | if (window.navigator.platform !== undefined) { | |
179 | // Object.defineProperty() doesn't work properly in old | |
180 | // versions of Chrome | |
181 | this.skip(); | |
182 | } | |
183 | ||
184 | window.navigator.platform = "Mac x86_64"; | |
185 | }); | |
186 | afterEach(function () { | |
187 | Object.defineProperty(window, "navigator", origNavigator); | |
188 | }); | |
189 | ||
190 | it('should change Alt to AltGraph', function() { | |
191 | var count = 0; | |
192 | var kbd = new Keyboard({ | |
193 | onKeyEvent: function(keysym, code, down) { | |
194 | switch (count++) { | |
195 | case 0: | |
196 | expect(keysym).to.be.equal(0xFF7E); | |
197 | expect(code).to.be.equal('AltLeft'); | |
198 | break; | |
199 | case 1: | |
200 | expect(keysym).to.be.equal(0xFE03); | |
201 | expect(code).to.be.equal('AltRight'); | |
202 | break; | |
203 | } | |
204 | }}); | |
9782d4a3 PO |
205 | kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1})); |
206 | kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); | |
bf43c263 PO |
207 | expect(count).to.be.equal(2); |
208 | }); | |
209 | it('should change left Super to Alt', function(done) { | |
210 | var kbd = new Keyboard({ | |
211 | onKeyEvent: function(keysym, code, down) { | |
212 | expect(keysym).to.be.equal(0xFFE9); | |
213 | expect(code).to.be.equal('MetaLeft'); | |
214 | done(); | |
215 | }}); | |
9782d4a3 | 216 | kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1})); |
bf43c263 PO |
217 | }); |
218 | it('should change right Super to left Super', function(done) { | |
219 | var kbd = new Keyboard({ | |
220 | onKeyEvent: function(keysym, code, down) { | |
221 | expect(keysym).to.be.equal(0xFFEB); | |
222 | expect(code).to.be.equal('MetaRight'); | |
223 | done(); | |
224 | }}); | |
9782d4a3 | 225 | kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2})); |
bf43c263 PO |
226 | }); |
227 | }); | |
228 | ||
229 | describe('Escape AltGraph on Windows', function() { | |
f7363fd2 PO |
230 | var origNavigator; |
231 | beforeEach(function () { | |
232 | // window.navigator is a protected read-only property in many | |
233 | // environments, so we need to redefine it whilst running these | |
234 | // tests. | |
235 | origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); | |
236 | if (origNavigator === undefined) { | |
237 | // Object.getOwnPropertyDescriptor() doesn't work | |
238 | // properly in any version of IE | |
239 | this.skip(); | |
240 | } | |
241 | ||
242 | Object.defineProperty(window, "navigator", {value: {}}); | |
243 | if (window.navigator.platform !== undefined) { | |
244 | // Object.defineProperty() doesn't work properly in old | |
245 | // versions of Chrome | |
246 | this.skip(); | |
247 | } | |
248 | ||
249 | window.navigator.platform = "Windows x86_64"; | |
250 | }); | |
251 | afterEach(function () { | |
252 | Object.defineProperty(window, "navigator", origNavigator); | |
253 | }); | |
254 | ||
bf43c263 | 255 | it('should generate fake undo/redo events on press when AltGraph is down', function() { |
f00b6fb6 | 256 | var times_called = 0; |
f7363fd2 PO |
257 | var kbd = new Keyboard({ |
258 | onKeyEvent: function(keysym, code, down) { | |
259 | switch(times_called++) { | |
260 | case 0: | |
261 | expect(keysym).to.be.equal(0xFFE3); | |
262 | expect(code).to.be.equal('ControlLeft'); | |
263 | expect(down).to.be.equal(true); | |
264 | break; | |
265 | case 1: | |
bf43c263 PO |
266 | expect(keysym).to.be.equal(0xFFEA); |
267 | expect(code).to.be.equal('AltRight'); | |
f7363fd2 PO |
268 | expect(down).to.be.equal(true); |
269 | break; | |
270 | case 2: | |
bf43c263 PO |
271 | expect(keysym).to.be.equal(0xFFEA); |
272 | expect(code).to.be.equal('AltRight'); | |
f7363fd2 PO |
273 | expect(down).to.be.equal(false); |
274 | break; | |
275 | case 3: | |
276 | expect(keysym).to.be.equal(0xFFE3); | |
bf43c263 | 277 | expect(code).to.be.equal('ControlLeft'); |
f7363fd2 PO |
278 | expect(down).to.be.equal(false); |
279 | break; | |
280 | case 4: | |
281 | expect(keysym).to.be.equal(0x61); | |
282 | expect(code).to.be.equal('KeyA'); | |
283 | expect(down).to.be.equal(true); | |
284 | break; | |
285 | case 5: | |
bf43c263 PO |
286 | expect(keysym).to.be.equal(0xFFE3); |
287 | expect(code).to.be.equal('ControlLeft'); | |
f7363fd2 PO |
288 | expect(down).to.be.equal(true); |
289 | break; | |
290 | case 6: | |
bf43c263 PO |
291 | expect(keysym).to.be.equal(0xFFEA); |
292 | expect(code).to.be.equal('AltRight'); | |
f7363fd2 PO |
293 | expect(down).to.be.equal(true); |
294 | break; | |
295 | } | |
296 | }}); | |
297 | // First the modifier combo | |
9782d4a3 PO |
298 | kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); |
299 | kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); | |
f7363fd2 PO |
300 | // Next a normal character |
301 | kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); | |
302 | expect(times_called).to.be.equal(7); | |
303 | }); | |
304 | it('should no do anything on key release', function() { | |
f00b6fb6 | 305 | var times_called = 0; |
f7363fd2 PO |
306 | var kbd = new Keyboard({ |
307 | onKeyEvent: function(keysym, code, down) { | |
308 | switch(times_called++) { | |
309 | case 7: | |
310 | expect(keysym).to.be.equal(0x61); | |
311 | expect(code).to.be.equal('KeyA'); | |
312 | expect(down).to.be.equal(false); | |
313 | break; | |
314 | } | |
315 | }}); | |
316 | // First the modifier combo | |
9782d4a3 PO |
317 | kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); |
318 | kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); | |
f7363fd2 PO |
319 | // Next a normal character |
320 | kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); | |
321 | kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); | |
322 | expect(times_called).to.be.equal(8); | |
323 | }); | |
324 | it('should not consider a char modifier to be down on the modifier key itself', function() { | |
f00b6fb6 | 325 | var times_called = 0; |
f7363fd2 PO |
326 | var kbd = new Keyboard({ |
327 | onKeyEvent: function(keysym, code, down) { | |
328 | switch(times_called++) { | |
329 | case 0: | |
330 | expect(keysym).to.be.equal(0xFFE3); | |
331 | expect(code).to.be.equal('ControlLeft'); | |
332 | expect(down).to.be.equal(true); | |
333 | break; | |
334 | case 1: | |
335 | expect(keysym).to.be.equal(0xFFE9); | |
336 | expect(code).to.be.equal('AltLeft'); | |
337 | expect(down).to.be.equal(true); | |
338 | break; | |
339 | case 2: | |
340 | expect(keysym).to.be.equal(0xFFE3); | |
341 | expect(code).to.be.equal('ControlLeft'); | |
342 | expect(down).to.be.equal(true); | |
343 | break; | |
344 | } | |
345 | }}); | |
346 | // First the modifier combo | |
9782d4a3 PO |
347 | kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); |
348 | kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1})); | |
f7363fd2 | 349 | // Then one of the keys again |
9782d4a3 | 350 | kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); |
f00b6fb6 | 351 | expect(times_called).to.be.equal(3); |
f00b6fb6 | 352 | }); |
353 | }); | |
354 | }); |