diff --git a/src/lib/strategies/AbstractKeyEventStrategy.js b/src/lib/strategies/AbstractKeyEventStrategy.js index 8c2a115f..68644c2a 100644 --- a/src/lib/strategies/AbstractKeyEventStrategy.js +++ b/src/lib/strategies/AbstractKeyEventStrategy.js @@ -167,6 +167,14 @@ class AbstractKeyEventStrategy { * @protected */ _initHandlerResolutionState() { + if (this.keyMaps === null) { + /** + * If this.keyMaps is already set to null, then the state has already been reset + * and we need not do it again + */ + return; + } + /** * List of mappings from key sequences to handlers that is constructed on-the-fly * as key events propagate up the render tree diff --git a/src/lib/strategies/GlobalKeyEventStrategy.js b/src/lib/strategies/GlobalKeyEventStrategy.js index e04e4e46..c39a04b8 100644 --- a/src/lib/strategies/GlobalKeyEventStrategy.js +++ b/src/lib/strategies/GlobalKeyEventStrategy.js @@ -73,6 +73,11 @@ class GlobalKeyEventStrategy extends AbstractKeyEventStrategy { this._updateDocumentHandlers(); + /** + * Reset handler resolution state + */ + this._initHandlerResolutionState(); + this.logger.debug( this._logPrefix(componentId, {eventId: false}), 'Mounted.', diff --git a/test/GlobalHotKeys/MatchingKeyMapAfterRemount.spec.js b/test/GlobalHotKeys/MatchingKeyMapAfterRemount.spec.js new file mode 100644 index 00000000..a208c238 --- /dev/null +++ b/test/GlobalHotKeys/MatchingKeyMapAfterRemount.spec.js @@ -0,0 +1,106 @@ +import {mount} from 'enzyme'; +import {expect} from 'chai'; +import sinon from 'sinon'; +import simulant from 'simulant'; +import React, {Component} from 'react' + +import KeyCode from '../support/Key'; +import {GlobalHotKeys} from '../../src'; + +describe('Matching key map after remount for a GlobalHotKeys component:', function () { + beforeEach(function () { + const keyMap = this.keyMap = { + 'ACTION_A': 'a', + }; + + const keyMap2 = this.keyMap2 = { + 'ACTION_B': 'b', + }; + + this.handler = sinon.spy(); + this.handler2 = sinon.spy(); + + const handlers = this.handlers = { + 'ACTION_A': this.handler, + }; + + const handlers2 = this.handlers2 = { + 'ACTION_B': this.handler2, + }; + + class ToggleComponent extends Component { + render(){ + const { secondKeyMapActive } = this.props; + + return ( +
+ + { secondKeyMapActive && } +
+ ); + } + } + + this.reactDiv = document.createElement('div'); + document.body.appendChild(this.reactDiv); + + this.wrapper = mount( + , + { attachTo: this.reactDiv } + ); + }); + + after(function() { + document.body.removeChild(this.reactDiv); + }); + + describe('when two GlobalHotKeys components are mounted, unmounted and remounted', () => { + it('then both of their key maps work while they are mounted and not, when they aren\'t (BUG: https://github.com/greena13/react-hotkeys/issues/150)', function() { + simulant.fire(this.reactDiv, 'keydown', { key: KeyCode.A }); + simulant.fire(this.reactDiv, 'keypress', { key: KeyCode.A }); + simulant.fire(this.reactDiv, 'keyup', { key: KeyCode.A }); + + expect(this.handler).to.have.been.calledOnce; + + simulant.fire(this.reactDiv, 'keydown', { key: KeyCode.B }); + simulant.fire(this.reactDiv, 'keypress', { key: KeyCode.B }); + simulant.fire(this.reactDiv, 'keyup', { key: KeyCode.B }); + + expect(this.handler2).to.have.been.calledOnce; + + /** + * Unmount the second GlobalHotKeys component + */ + this.wrapper.setProps({ secondKeyMapActive: false }); + + simulant.fire(this.reactDiv, 'keydown', { key: KeyCode.A }); + simulant.fire(this.reactDiv, 'keypress', { key: KeyCode.A }); + simulant.fire(this.reactDiv, 'keyup', { key: KeyCode.A }); + + expect(this.handler).to.have.been.calledTwice; + + simulant.fire(this.reactDiv, 'keydown', { key: KeyCode.B }); + simulant.fire(this.reactDiv, 'keypress', { key: KeyCode.B }); + simulant.fire(this.reactDiv, 'keyup', { key: KeyCode.B }); + + expect(this.handler2).to.have.been.calledOnce; + + /** + * Re-mount the second GlobalHotKeys component + */ + this.wrapper.setProps({ secondKeyMapActive: true }); + + simulant.fire(this.reactDiv, 'keydown', { key: KeyCode.A }); + simulant.fire(this.reactDiv, 'keypress', { key: KeyCode.A }); + simulant.fire(this.reactDiv, 'keyup', { key: KeyCode.A }); + + expect(this.handler).to.have.been.calledThrice; + + simulant.fire(this.reactDiv, 'keydown', { key: KeyCode.B }); + simulant.fire(this.reactDiv, 'keypress', { key: KeyCode.B }); + simulant.fire(this.reactDiv, 'keyup', { key: KeyCode.B }); + + expect(this.handler2).to.have.been.calledTwice; + }); + }); +});