1 <!doctype html> 2 <!-- 3 @license 4 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 5 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 6 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 7 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 8 Code distributed by Google as part of the polymer project is also 9 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 10 --> 11 <html> 12 <head> 13 <meta charset="UTF-8"> 14 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> 15 <title>iron-a11y-keys</title> 16 17 <script src="../../webcomponentsjs/webcomponents-lite.js"></script> 18 <script src="../../web-component-tester/browser.js"></script> 19 <script src="../../iron-test-helpers/mock-interactions.js"></script> 20 21 <link rel="import" href="../../polymer/polymer.html"> 22 <link rel="import" href="../iron-a11y-keys-behavior.html"> 23 </head> 24 <body> 25 <test-fixture id="BasicKeys"> 26 <template> 27 <x-a11y-basic-keys></x-a11y-basic-keys> 28 </template> 29 </test-fixture> 30 31 <test-fixture id="NonPropagatingKeys"> 32 <template> 33 <x-a11y-basic-keys stop-keyboard-event-propagation></x-a11y-basic-keys> 34 </template> 35 </test-fixture> 36 37 <test-fixture id="ComboKeys"> 38 <template> 39 <x-a11y-combo-keys></x-a11y-combo-keys> 40 </template> 41 </test-fixture> 42 43 <test-fixture id="AlternativeEventKeys"> 44 <template> 45 <x-a11y-alternate-event-keys></x-a11y-alternate-event-keys> 46 </template> 47 </test-fixture> 48 49 <test-fixture id="BehaviorKeys"> 50 <template> 51 <x-a11y-behavior-keys></x-a11y-behavior-keys> 52 </template> 53 </test-fixture> 54 55 <test-fixture id="PreventKeys"> 56 <template> 57 <x-a11y-prevent-keys></x-a11y-prevent-keys> 58 </template> 59 </test-fixture> 60 61 <script> 62 suite('Polymer.IronA11yKeysBehavior', function() { 63 var keys; 64 65 suiteSetup(function() { 66 var KeysTestBehavior = [Polymer.IronA11yKeysBehavior, { 67 properties: { 68 keyCount: { 69 type: Number, 70 value: 0 71 } 72 }, 73 74 _keyHandler: function(event) { 75 this.keyCount++; 76 this.lastEvent = event; 77 }, 78 79 // Same as _keyHandler, used to distinguish who's called before who. 80 _keyHandler2: function(event) { 81 this.keyCount++; 82 this.lastEvent = event; 83 }, 84 85 _preventDefaultHandler: function(event) { 86 event.preventDefault(); 87 this.keyCount++; 88 this.lastEvent = event; 89 } 90 }]; 91 92 Polymer({ 93 is: 'x-a11y-basic-keys', 94 95 behaviors: [ 96 KeysTestBehavior 97 ], 98 99 keyBindings: { 100 'space': '_keyHandler', 101 '@': '_keyHandler', 102 'esc': '_keyHandler' 103 } 104 }); 105 106 Polymer({ 107 is: 'x-a11y-combo-keys', 108 109 behaviors: [ 110 KeysTestBehavior 111 ], 112 113 keyBindings: { 114 'enter': '_keyHandler2', 115 'ctrl+shift+a shift+enter': '_keyHandler' 116 } 117 }); 118 119 Polymer({ 120 is: 'x-a11y-alternate-event-keys', 121 122 behaviors: [ 123 KeysTestBehavior 124 ], 125 126 keyBindings: { 127 'space:keyup': '_keyHandler' 128 } 129 }); 130 131 var XA11yBehavior = { 132 keyBindings: { 133 'enter': '_keyHandler' 134 } 135 }; 136 137 Polymer({ 138 is: 'x-a11y-behavior-keys', 139 140 behaviors: [ 141 KeysTestBehavior, 142 XA11yBehavior 143 ], 144 145 keyBindings: { 146 'enter': '_keyHandler' 147 } 148 }); 149 150 Polymer({ 151 is: 'x-a11y-prevent-keys', 152 153 behaviors: [ 154 KeysTestBehavior, 155 XA11yBehavior 156 ], 157 158 keyBindings: { 159 'space a': '_keyHandler', 160 'enter shift+a': '_preventDefaultHandler' 161 } 162 }); 163 }); 164 165 suite('basic keys', function() { 166 setup(function() { 167 keys = fixture('BasicKeys'); 168 }); 169 170 test('trigger the handler when the specified key is pressed', function() { 171 MockInteractions.pressSpace(keys); 172 173 expect(keys.keyCount).to.be.equal(1); 174 }); 175 176 test('keyEventTarget can be null, and disables listeners', function() { 177 keys.keyEventTarget = null; 178 MockInteractions.pressSpace(keys); 179 180 expect(keys.keyCount).to.be.equal(0); 181 }); 182 183 test('trigger the handler when the specified key is pressed together with a modifier', function() { 184 var event = new CustomEvent('keydown'); 185 event.ctrlKey = true; 186 event.keyCode = event.code = 32; 187 keys.dispatchEvent(event); 188 expect(keys.keyCount).to.be.equal(1); 189 }); 190 191 test('handles special character @', function() { 192 MockInteractions.pressAndReleaseKeyOn(keys, undefined, [], '@'); 193 194 expect(keys.keyCount).to.be.equal(1); 195 }); 196 197 test('handles variations of Esc key', function() { 198 MockInteractions.pressAndReleaseKeyOn(keys, undefined, [], 'Esc'); 199 expect(keys.keyCount).to.be.equal(1); 200 201 MockInteractions.pressAndReleaseKeyOn(keys, undefined, [], 'Escape'); 202 expect(keys.keyCount).to.be.equal(2); 203 204 MockInteractions.pressAndReleaseKeyOn(keys, 27, [], ''); 205 expect(keys.keyCount).to.be.equal(3); 206 }); 207 208 test('do not trigger the handler for non-specified keys', function() { 209 MockInteractions.pressEnter(keys); 210 211 expect(keys.keyCount).to.be.equal(0); 212 }); 213 214 test('can have bindings added imperatively', function() { 215 keys.addOwnKeyBinding('enter', '_keyHandler'); 216 217 MockInteractions.pressEnter(keys); 218 expect(keys.keyCount).to.be.equal(1); 219 220 MockInteractions.pressSpace(keys); 221 expect(keys.keyCount).to.be.equal(2); 222 }); 223 224 test('can remove imperatively added bindings', function() { 225 keys.addOwnKeyBinding('enter', '_keyHandler'); 226 keys.removeOwnKeyBindings(); 227 228 MockInteractions.pressEnter(keys); 229 expect(keys.keyCount).to.be.equal(0); 230 231 MockInteractions.pressSpace(keys); 232 expect(keys.keyCount).to.be.equal(1); 233 }); 234 235 test('allows propagation beyond the key combo handler', function() { 236 var keySpy = sinon.spy(); 237 document.addEventListener('keydown', keySpy); 238 239 MockInteractions.pressEnter(keys); 240 241 expect(keySpy.callCount).to.be.equal(1); 242 }); 243 244 suite('edge cases', function() { 245 test('knows that `spacebar` is the same as `space`', function() { 246 var event = new CustomEvent('keydown'); 247 event.key = 'spacebar'; 248 expect(keys.keyboardEventMatchesKeys(event, 'space')).to.be.equal(true); 249 }); 250 251 test('handles `+`', function() { 252 var event = new CustomEvent('keydown'); 253 event.key = '+'; 254 expect(keys.keyboardEventMatchesKeys(event, '+')).to.be.equal(true); 255 }); 256 257 test('handles `:`', function() { 258 var event = new CustomEvent('keydown'); 259 event.key = ':'; 260 expect(keys.keyboardEventMatchesKeys(event, ':')).to.be.equal(true); 261 }); 262 263 test('handles ` ` (space)', function() { 264 var event = new CustomEvent('keydown'); 265 event.key = ' '; 266 expect(keys.keyboardEventMatchesKeys(event, 'space')).to.be.equal(true); 267 }); 268 }); 269 270 suite('matching keyboard events to keys', function() { 271 test('can be done imperatively', function() { 272 var event = new CustomEvent('keydown'); 273 event.keyCode = 65; 274 expect(keys.keyboardEventMatchesKeys(event, 'a')).to.be.equal(true); 275 }); 276 277 test('can be done with a provided keyboardEvent', function() { 278 var event; 279 MockInteractions.pressSpace(keys); 280 event = keys.lastEvent; 281 282 expect(event.detail.keyboardEvent).to.be.okay; 283 expect(keys.keyboardEventMatchesKeys(event, 'space')).to.be.equal(true); 284 }); 285 286 test('can handle variations in arrow key names', function() { 287 var event = new CustomEvent('keydown'); 288 event.key = 'up'; 289 expect(keys.keyboardEventMatchesKeys(event, 'up')).to.be.equal(true); 290 event.key = 'ArrowUp'; 291 expect(keys.keyboardEventMatchesKeys(event, 'up')).to.be.equal(true); 292 }); 293 }); 294 295 suite('matching keyboard events to top row and number pad digit keys', function() { 296 test('top row can be done imperatively', function() { 297 var event = new CustomEvent('keydown'); 298 event.keyCode = 49; 299 expect(keys.keyboardEventMatchesKeys(event, '1')).to.be.equal(true); 300 }); 301 302 test('number pad digits can be done imperatively', function() { 303 var event = new CustomEvent('keydown'); 304 event.keyCode = 97; 305 expect(keys.keyboardEventMatchesKeys(event, '1')).to.be.equal(true); 306 }); 307 }); 308 }); 309 310 suite('combo keys', function() { 311 setup(function() { 312 keys = fixture('ComboKeys'); 313 }); 314 315 test('trigger the handler when the combo is pressed', function() { 316 var event = new CustomEvent('keydown'); 317 318 event.ctrlKey = true; 319 event.shiftKey = true; 320 event.keyCode = event.code = 65; 321 322 keys.dispatchEvent(event); 323 324 expect(keys.keyCount).to.be.equal(1); 325 }); 326 327 test('check if KeyBoardEvent.key is alpha-numberic', function() { 328 var event = new CustomEvent('keydown'); 329 330 event.ctrlKey = true; 331 event.shiftKey = true; 332 event.key = ''; 333 event.keyCode = event.code = 65; 334 335 keys.dispatchEvent(event); 336 337 expect(keys.keyCount).to.be.equal(1); 338 }); 339 340 test('trigger also bindings without modifiers', function() { 341 var event = new CustomEvent('keydown'); 342 // Combo `shift+enter`. 343 event.shiftKey = true; 344 event.keyCode = event.code = 13; 345 keys.dispatchEvent(event); 346 expect(keys.keyCount).to.be.equal(2); 347 }); 348 349 test('give precendence to combos with modifiers', function() { 350 var enterSpy = sinon.spy(keys, '_keyHandler2'); 351 var shiftEnterSpy = sinon.spy(keys, '_keyHandler'); 352 var event = new CustomEvent('keydown'); 353 // Combo `shift+enter`. 354 event.shiftKey = true; 355 event.keyCode = event.code = 13; 356 keys.dispatchEvent(event); 357 expect(enterSpy.called).to.be.true; 358 expect(shiftEnterSpy.called).to.be.true; 359 expect(enterSpy.calledAfter(shiftEnterSpy)).to.be.true; 360 }); 361 362 }); 363 364 suite('alternative event keys', function() { 365 setup(function() { 366 keys = fixture('AlternativeEventKeys'); 367 }); 368 369 test('trigger on the specified alternative keyboard event', function() { 370 MockInteractions.keyDownOn(keys, 32); 371 372 expect(keys.keyCount).to.be.equal(0); 373 374 MockInteractions.keyUpOn(keys, 32); 375 376 expect(keys.keyCount).to.be.equal(1); 377 }); 378 }); 379 380 suite('behavior keys', function() { 381 setup(function() { 382 keys = fixture('BehaviorKeys'); 383 }); 384 385 test('bindings in other behaviors are transitive', function() { 386 MockInteractions.pressEnter(keys); 387 expect(keys.keyCount).to.be.equal(2); 388 }); 389 }); 390 391 suite('stopping propagation automatically', function() { 392 setup(function() { 393 keys = fixture('NonPropagatingKeys'); 394 }); 395 396 test('does not propagate key events beyond the combo handler', function() { 397 var keySpy = sinon.spy(); 398 399 document.addEventListener('keydown', keySpy); 400 401 MockInteractions.pressEnter(keys); 402 403 expect(keySpy.callCount).to.be.equal(0); 404 }); 405 }); 406 407 suite('prevent default behavior of event', function() { 408 setup(function() { 409 keys = fixture('PreventKeys'); 410 }); 411 412 test('`defaultPrevented` is correctly set', function() { 413 MockInteractions.pressEnter(keys); 414 expect(keys.lastEvent.defaultPrevented).to.be.equal(true); 415 }); 416 417 test('only 1 handler is invoked', function() { 418 var aSpy = sinon.spy(keys, '_keyHandler'); 419 var shiftASpy = sinon.spy(keys, '_preventDefaultHandler'); 420 var event = new CustomEvent('keydown', { 421 cancelable: true 422 }); 423 // Combo `shift+a`. 424 event.shiftKey = true; 425 event.keyCode = event.code = 65; 426 keys.dispatchEvent(event); 427 428 expect(keys.keyCount).to.be.equal(1); 429 expect(shiftASpy.called).to.be.true; 430 expect(aSpy.called).to.be.false; 431 }); 432 }); 433 434 suite('remove key behavior with null target', function () { 435 test('add and remove a iron-a11y-keys-behavior', function () { 436 var element = document.createElement('x-a11y-basic-keys'); 437 element.keyEventTarget = null; 438 document.body.appendChild(element); 439 document.body.removeChild(element); 440 }); 441 }); 442 443 }); 444 </script> 445 </body> 446 </html> 447