describe 'Angular Hotkeys', -> hotkeys = scope = $rootScope = $rootElement = $window = null beforeEach -> module 'cfp.hotkeys', (hotkeysProvider) -> hotkeysProvider.useNgRoute = true return result = null inject (_$rootElement_, _$rootScope_, _hotkeys_) -> hotkeys = _hotkeys_ $rootElement = _$rootElement_ $rootScope = _$rootScope_ scope = $rootScope.$new() afterEach -> hotkeys.del('w') t = document.getElementById('cfp-test') if t t.parentNode.removeChild(t) it 'should insert the help menu into the dom', -> children = angular.element($rootElement).children() expect(children.hasClass('cfp-hotkeys-container')).toBe true it 'add(args)', -> hotkeys.add 'w', 'description here', -> expect(hotkeys.get('w').description).toBe 'description here' expect(hotkeys.get('x')).toBe false it 'add(object)', -> callback = false hotkeys.add combo: 'w' description: 'description' callback: () -> callback = true expect(hotkeys.get('w').description).toBe 'description' # Test callback: expect(callback).toBe false KeyEvent.simulate('w'.charCodeAt(0), 90) expect(callback).toBe true it 'description should be optional', -> # func argument style: hotkeys.add 'w', -> expect(hotkeys.get('w').description).toBe '$$undefined$$' # object style: hotkeys.add combo: 'e' callback: -> expect(hotkeys.get('e').description).toBe '$$undefined$$' it 'del()', -> hotkeys.add 'w', -> expect(hotkeys.get('w').description).toBe '$$undefined$$' hotkeys.del 'w' expect(hotkeys.get('w')).toBe false it 'should toggle help when ? is pressed', -> expect(angular.element($rootElement).children().hasClass('in')).toBe false KeyEvent.simulate('?'.charCodeAt(0), 90) expect(angular.element($rootElement).children().hasClass('in')).toBe true it 'should bind esc when the cheatsheet is shown', -> expect(hotkeys.get('esc')).toBe false expect(angular.element($rootElement).children().hasClass('in')).toBe false KeyEvent.simulate('?'.charCodeAt(0), 90) expect(angular.element($rootElement).children().hasClass('in')).toBe true expect(hotkeys.get('esc').combo).toEqual ['esc'] KeyEvent.simulate('?'.charCodeAt(0), 90) expect(hotkeys.get('esc')).toBe false it 'should remember previously bound ESC when cheatsheet is shown', -> expect(hotkeys.get('esc')).toBe false # bind something to escape: hotkeys.add 'esc', 'temp', () -> expect(hotkeys.get('esc').description).toBe 'temp' originalCallback = hotkeys.get('esc').callback # show the cheat-sheet which will overwrite the esc key. however, we want to # show the original combo description in the callback, yet have the new # callback bound to remove the cheatsheet from view. KeyEvent.simulate('?'.charCodeAt(0), 90) expect(hotkeys.get('esc').description).toBe 'temp' expect(hotkeys.get('esc').callback).not.toBe originalCallback # hide the cheat sheet to verify the previous esc binding is back KeyEvent.simulate('?'.charCodeAt(0), 90) expect(hotkeys.get('esc').description).toBe 'temp' it 'should (un)bind based on route changes', -> # fake a route change: expect(hotkeys.get('w e s')).toBe false $rootScope.$broadcast('$routeChangeSuccess', { hotkeys: [['w e s', 'Do something Amazing!', 'callme("ishmael")']] }); expect(hotkeys.get('w e s').combo).toEqual ['w e s'] # ensure hotkey is unbound when the route changes $rootScope.$broadcast('$routeChangeSuccess', {}); expect(hotkeys.get('w e s')).toBe false it 'should callback when the hotkey is pressed', -> executed = false hotkeys.add 'w', -> executed = true KeyEvent.simulate('w'.charCodeAt(0), 90) expect(executed).toBe true it 'should callback according to action', -> keypressA = false; keypressB = false; hotkeys.add 'a', -> keypressA = true , 'keyup' hotkeys.add 'b', -> keypressB = true KeyEvent.simulate('a'.charCodeAt(0), 90) KeyEvent.simulate('b'.charCodeAt(0), 90) expect(keypressA).toBe false expect(keypressB).toBe true expect(hotkeys.get('a').action).toBe 'keyup' it 'should allow to invoke hotkey.callback programmatically without event object', -> called = false; hotkeys.add 'a', -> called = true , 'keyup' hotkeys.get('a').callback() expect(called).toBe true it 'should run routes-defined hotkey callbacks when scope is available', -> executed = false passedArg = null $rootScope.callme = (arg) -> executed = true passedArg = arg $rootScope.$broadcast '$routeChangeSuccess', hotkeys: [['w', 'Do something Amazing!', 'callme("ishmael")']] scope: $rootScope expect(executed).toBe false KeyEvent.simulate('w'.charCodeAt(0), 90) expect(executed).toBe true expect(passedArg).toBe 'ishmael' it 'should callback when hotkey is pressed in input field and allowIn INPUT is configured', -> executed = no $body = angular.element document.body $input = angular.element '' $body.prepend $input hotkeys.add combo: 'w' allowIn: ['INPUT'] callback: -> executed = yes KeyEvent.simulate('w'.charCodeAt(0), 90, undefined, $input[0]) expect(executed).toBe yes it 'should callback when hotkey is pressed in select field and allowIn SELECT is configured', -> executed = no $body = angular.element document.body $select = angular.element '' $body.prepend $select hotkeys.add combo: 'w' allowIn: ['SELECT'] callback: -> executed = yes KeyEvent.simulate('w'.charCodeAt(0), 90, undefined, $select[0]) expect(executed).toBe yes it 'should callback when hotkey is pressed in textarea field and allowIn TEXTAREA is configured', -> executed = no $body = angular.element document.body $textarea = angular.element '' $body.prepend $textarea hotkeys.add combo: 'w' allowIn: ['TEXTAREA'] callback: -> executed = yes KeyEvent.simulate('w'.charCodeAt(0), 90, undefined, $textarea[0]) expect(executed).toBe yes it 'should not callback when hotkey is pressed in input field without allowIn INPUT', -> executed = no $body = angular.element document.body $input = angular.element '' $body.prepend $input hotkeys.add combo: 'w' callback: -> executed = yes KeyEvent.simulate('w'.charCodeAt(0), 90, undefined, $input[0]) expect(executed).toBe no it 'should not callback when hotkey is pressed in select field without allowIn SELECT', -> executed = no $body = angular.element document.body $select = angular.element '' $body.prepend $select hotkeys.add combo: 'w' callback: -> executed = yes KeyEvent.simulate('w'.charCodeAt(0), 90, undefined, $select[0]) expect(executed).toBe no it 'should not callback when hotkey is pressed in textarea field without allowIn TEXTAREA', -> executed = no $body = angular.element document.body $textarea = angular.element '' $body.prepend $textarea hotkeys.add combo: 'w' callback: -> executed = yes KeyEvent.simulate('w'.charCodeAt(0), 90, undefined, $textarea[0]) expect(executed).toBe no it 'should callback when the mousetrap class is present', -> executed = no $body = angular.element document.body $input = angular.element '' $body.prepend $input hotkeys.add combo: 'a' callback: -> executed = yes KeyEvent.simulate('a'.charCodeAt(0), 90, undefined, $input[0]) expect(executed).toBe yes it 'should be capable of binding to a scope and auto-destroy itself', -> hotkeys.bindTo(scope) .add combo: ['w', 'e', 's'] description: 'description for w' callback: () -> persistent: false .add combo: 'a' action: 'keyup' description: 'description for a', callback: () -> .add('b', 'description for b', () ->) .add('c', 'description for c', () ->) expect(hotkeys.get('w').combo).toEqual ['w', 'e', 's'] expect(hotkeys.get('e').combo).toEqual ['w', 'e', 's'] expect(hotkeys.get('a').combo).toEqual ['a'] expect(hotkeys.get('b').combo).toEqual ['b'] expect(hotkeys.get('c').combo).toEqual ['c'] scope.$destroy() expect(hotkeys.get('w')).toBe false expect(hotkeys.get('e')).toBe false expect(hotkeys.get('s')).toBe false expect(hotkeys.get('a')).toBe false expect(hotkeys.get('b')).toBe false expect(hotkeys.get('c')).toBe false it 'should allow multiple calls to bindTo for same scope and still be auto-destroying', -> hotkeys.bindTo(scope) .add combo: ['w', 'e', 's'] description: 'description for w' callback: () -> persistent: false hotkeys.bindTo(scope) .add combo: 'a' action: 'keyup' description: 'description for a', callback: () -> .add('b', 'description for b', () ->) .add('c', 'description for c', () ->) expect(hotkeys.get('w').combo).toEqual ['w', 'e', 's'] expect(hotkeys.get('e').combo).toEqual ['w', 'e', 's'] expect(hotkeys.get('a').combo).toEqual ['a'] expect(hotkeys.get('b').combo).toEqual ['b'] expect(hotkeys.get('c').combo).toEqual ['c'] scope.$destroy() expect(hotkeys.get('w')).toBe false expect(hotkeys.get('e')).toBe false expect(hotkeys.get('s')).toBe false expect(hotkeys.get('a')).toBe false expect(hotkeys.get('b')).toBe false expect(hotkeys.get('c')).toBe false it 'should support pause/unpause for temporary disabling of hotkeys', -> executed = false hotkeys.add 'w', -> executed = true hotkeys.pause() KeyEvent.simulate('w'.charCodeAt(0), 90) expect(executed).toBe false hotkeys.unpause() KeyEvent.simulate('w'.charCodeAt(0), 90) expect(executed).toBe true describe 'misc regression tests', -> # allowIn arguments were not aligned (issue #40 and #36) so test to prevent regressions: it 'should unbind hotkeys that have also set allowIn (#36, #40)', -> # func argument style should be deprecated soon hotkeys.add 't', 'testing', () -> test = true , undefined, undefined, false hotkeys.add combo: 'w' description: 'description' callback: () -> persistent: false expect(hotkeys.get('t').combo).toEqual ['t'] expect(hotkeys.get('w').combo).toEqual ['w'] expect(hotkeys.get('t').persistent).toBe false expect(hotkeys.get('w').persistent).toBe false $rootScope.$broadcast('$routeChangeSuccess', {}); expect(hotkeys.get('t')).toBe false expect(hotkeys.get('w')).toBe false it '#42 closing cheatsheet with x should use toggleCheatSheet so esc is unbound', -> expect(hotkeys.get('esc')).toBe false expect(angular.element($rootElement).children().hasClass('in')).toBe false KeyEvent.simulate('?'.charCodeAt(0), 90) expect(angular.element($rootElement).children().hasClass('in')).toBe true expect(hotkeys.get('esc').combo).toEqual ['esc'] # hotkeys.toggleCheatSheet() scope.$$prevSibling.toggleCheatSheet() expect(hotkeys.get('esc')).toBe false describe 'multiple bindings', -> it 'get()', -> hotkeys.add ['a', 'b', 'c'], -> # Make sure they were added: expect(hotkeys.get('a').combo).toEqual ['a', 'b', 'c'] expect(hotkeys.get('b').combo).toEqual ['a', 'b', 'c'] expect(hotkeys.get('c').combo).toEqual ['a', 'b', 'c'] expect(hotkeys.get('w')).toBe false # expect(hotkeys.get(['a', 'b'])).toEqual ['a', 'b', 'c'] it 'should callback', -> executeCount = 0 hotkeys.add ['a', 'b', 'c'], -> executeCount++ # Make sure they work: KeyEvent.simulate('a'.charCodeAt(0), 90) expect(executeCount).toBe 1 KeyEvent.simulate('b'.charCodeAt(0), 90) expect(executeCount).toBe 2 KeyEvent.simulate('c'.charCodeAt(0), 90) expect(executeCount).toBe 3 KeyEvent.simulate('w'.charCodeAt(0), 90) expect(executeCount).toBe 3 it 'should delete', -> hotkeys.add ['w', 'e', 's'], -> expect(hotkeys.get('w').combo).toEqual ['w', 'e', 's'] expect(hotkeys.del(['w', 'f'])).toBe false expect(hotkeys.get('w')).toBe false expect(hotkeys.get('e').combo).toEqual ['e', 's'] expect(hotkeys.del(['e', 's'])).toBe true it 'should still callback when some combos remain', -> executeCount = 0 hotkeys.add ['a', 'b', 'c'], -> executeCount++ # Delete, but leave a hotkey: hotkeys.del ['a', 'b'] KeyEvent.simulate('b'.charCodeAt(0), 90) expect(executeCount).toBe 0 KeyEvent.simulate('c'.charCodeAt(0), 90) expect(executeCount).toBe 1 expect(hotkeys.get('a')).toBe false expect(hotkeys.get('b')).toBe false expect(hotkeys.get('c').combo).toEqual ['c'] it '#49 regression test', -> hotkeys.add combo: ['1','2','3','4','5','6','7','8','9'] description: 'ensure no regressions' callback: () -> expect(hotkeys.get('1').combo).toEqual ['1','2','3','4','5','6','7','8','9'] hotkeys.del(['1','2','3','4','5','6','7','8','9']) expect(hotkeys.get('1')).toBe false describe 'hotkey directive', -> elSimple = elAllowIn = elMultiple = scope = hotkeys = $compile = $document = executedSimple = executedAllowIn = null beforeEach -> module('cfp.hotkeys') executedSimple = no executedAllowIn = no inject ($rootScope, _$compile_, _$document_, _hotkeys_) -> hotkeys = _hotkeys_ $compile = _$compile_ # el = angular.element() scope = $rootScope.$new() scope.callmeSimple = () -> executedSimple = yes scope.callmeAllowIn = () -> executedAllowIn = yes scope.callmeMultiple = () -> elSimple = $compile('
')(scope) elAllowIn = $compile('')(scope) elMultiple = $compile('')(scope) scope.$digest() it 'should allow hotkey binding via directive', -> expect(hotkeys.get('e').combo).toEqual ['e'] expect(hotkeys.get('w').combo).toEqual ['w'] expect(executedSimple).toBe no expect(executedAllowIn).toBe no KeyEvent.simulate('e'.charCodeAt(0), 90) KeyEvent.simulate('w'.charCodeAt(0), 90) expect(executedSimple).toBe yes expect(executedAllowIn).toBe yes it 'should accept allowIn arguments', -> $body = angular.element document.body $input = angular.element '' $body.prepend $input expect(executedAllowIn).toBe no KeyEvent.simulate('w'.charCodeAt(0), 90) expect(executedAllowIn).toBe yes expect(hotkeys.get('w').allowIn).toEqual ['INPUT', 'TEXTAREA'] it 'should unbind the hotkey when the directive is destroyed', -> expect(hotkeys.get('e').combo).toEqual ['e'] expect(hotkeys.get('w').combo).toEqual ['w'] expect(hotkeys.get('a').combo).toEqual ['a'] expect(hotkeys.get('b').combo).toEqual ['b'] elSimple.remove() elAllowIn.remove() elMultiple.remove() expect(hotkeys.get('e')).toBe no expect(hotkeys.get('w')).toBe no expect(hotkeys.get('a')).toBe no expect(hotkeys.get('b')).toBe no describe 'Platform specific things', -> beforeEach -> windowMock = navigator: platform: 'Macintosh' module 'cfp.hotkeys' it 'should display mac key combos', -> module ($provide) -> $provide.value '$window', angular.extend window, navigator: platform: 'Macintosh' return inject (hotkeys) -> hotkeys.add 'mod+e', 'description' expect(hotkeys.get('mod+e').format()[0]).toBe '⌘ + e' it 'should display win/linux key combos', -> module ($provide) -> $provide.value '$window', angular.extend window, navigator: platform: 'Linux x86_64' return inject (hotkeys) -> hotkeys.add 'mod+e', 'description' expect(hotkeys.get('mod+e').format()[0]).toBe 'ctrl + e' describe 'Configuration options', -> it 'should disable the cheatsheet when configured', -> module 'cfp.hotkeys', (hotkeysProvider) -> hotkeysProvider.includeCheatSheet = false return inject ($rootElement, hotkeys) -> children = angular.element($rootElement).children() expect(children.length).toBe 0 it 'should enable the cheatsheet when configured', -> module 'cfp.hotkeys', (hotkeysProvider) -> hotkeysProvider.includeCheatSheet = true return inject ($rootElement, hotkeys) -> children = angular.element($rootElement).children() expect(children.length).toBe 1 it 'should accept an alternate template to inject', -> module 'cfp.hotkeys', (hotkeysProvider) -> hotkeysProvider.template = '