diff --git a/mocha.opts b/mocha.opts
new file mode 100644
index 0000000..6c66a0c
--- /dev/null
+++ b/mocha.opts
@@ -0,0 +1,2 @@
+--recursive
+--require test/setup.js
diff --git a/package.json b/package.json
index 1963005..37ec9cc 100644
--- a/package.json
+++ b/package.json
@@ -53,11 +53,12 @@
"babel-preset-react": "^6.11.1",
"babel-preset-react-optimize": "^1.0.1",
"babel-preset-stage-0": "^6.5.0",
- "chai": "^3.5.0",
- "chai-enzyme": "^0.5.0",
- "cheerio": "^0.20.0",
+ "chai": "^4.1.2",
+ "chai-enzyme": "1.0.0-beta.0",
+ "cheerio": "^1.0.0-0",
"css-loader": "^0.15.6",
- "enzyme": "^2.4.1",
+ "enzyme": "^3.1.0",
+ "enzyme-adapter-react-15": "^1.0.1",
"eslint": "^3.10.2",
"eslint-config-airbnb": "^13.0.0",
"eslint-import-resolver-webpack": "^0.7.0",
@@ -71,9 +72,11 @@
"less-loader": "^2.2.0",
"lodash": "^4.15.0",
"mocha": "^2.2.5",
- "react": "^0.14.8 || ^15",
+ "react": "^15.6.2",
"react-addons-test-utils": "^0.14.8 || ^15",
- "react-dom": "^0.14.8 || ^15",
+ "react-dom": "^15.5.0",
+ "react-dom-factories": "^1.0.2",
+ "react-test-renderer": "^15.5.0",
"simulant": "^0.2.2",
"sinon": "^1.17.5",
"sinon-chai": "^2.8.0",
diff --git a/test/keymap.js b/test/keymap.js
index 3c1c285..36b1a8e 100644
--- a/test/keymap.js
+++ b/test/keymap.js
@@ -24,5 +24,9 @@ export default {
'OPEN': 'enter',
'CLOSE': 'esc',
},
- 'NON-EXISTING': {},
-}
+ 'PARENT': {
+ 'OPEN': 'enter',
+ 'NEXT': 'tab',
+ },
+ 'NO-SHORTCUTS': {},
+};
diff --git a/test/setup.js b/test/setup.js
new file mode 100644
index 0000000..a67022f
--- /dev/null
+++ b/test/setup.js
@@ -0,0 +1,36 @@
+/**
+ * This file initialises the test environment with tools used for
+ * defining the test suite.
+ *
+ * It is run once before the test suite is executed and should only
+ * include setup code that is applicable to the entire suite.
+ *
+ * For configuration options of mocha itself, see the mocha.opts file.
+ */
+
+// React testing framework for traversing React components' output
+import Enzyme from 'enzyme';
+import Adaptor from 'enzyme-adapter-react-15';
+
+// Assertion library for more expressive syntax
+import chai from 'chai';
+
+// chai plugin that allows React-specific assertions for enzyme
+import chaiEnzyme from 'chai-enzyme';
+
+// chai plugin that allows assertions on function calls
+import sinonChai from 'sinon-chai';
+
+// JS implementation of DOM and HTML spec
+import jsdom from 'jsdom';
+
+chai.use(chaiEnzyme());
+chai.use(sinonChai);
+
+Enzyme.configure({ adapter: new Adaptor() });
+
+global.document = jsdom.jsdom('
');
+global.window = document.defaultView;
+global.Image = window.Image;
+global.navigator = window.navigator;
+global.CustomEvent = window.CustomEvent;
diff --git a/test/shortcut-manager.spec.js b/test/shortcut-manager.spec.js
index 67b4bb4..2a7cbc6 100644
--- a/test/shortcut-manager.spec.js
+++ b/test/shortcut-manager.spec.js
@@ -1,47 +1,42 @@
-import jsdom from 'jsdom'
-import chai from 'chai'
-import _ from 'lodash'
-import sinonChai from 'sinon-chai'
-import sinon from 'sinon'
+import chai from 'chai';
+import _ from 'lodash';
+import sinon from 'sinon';
-import keymap from './keymap'
+import simulant from 'simulant';
-chai.use(sinonChai)
+import keymap from './keymap';
-const { expect } = chai
+const { expect } = chai;
+
+import renderComponent from './support/renderComponent';
+import KeyCodes from './support/KeyCodes';
describe('Shortcut manager', () => {
- let ShortcutManager = null
+ let ShortcutManager = null;
before(() => {
- global.document = jsdom.jsdom('')
- global.window = document.defaultView
- global.Image = window.Image
- global.navigator = window.navigator
- global.CustomEvent = window.CustomEvent
-
- ShortcutManager = require('../src').ShortcutManager
- })
+ ShortcutManager = require('../src').ShortcutManager;
+ });
it('should return empty object when calling empty constructor', () => {
- const manager = new ShortcutManager()
- expect(manager.getAllShortcuts()).to.be.empty
- })
+ const manager = new ShortcutManager();
+ expect(manager.getAllShortcuts()).to.be.empty;
+ });
it('should return all shortcuts', () => {
- const manager = new ShortcutManager(keymap)
- expect(manager.getAllShortcuts()).to.not.be.empty
- expect(manager.getAllShortcuts()).to.be.equal(keymap)
+ const manager = new ShortcutManager(keymap);
+ expect(manager.getAllShortcuts()).to.not.be.empty;
+ expect(manager.getAllShortcuts()).to.be.equal(keymap);
- manager.setKeymap({})
- expect(manager.getAllShortcuts()).to.be.empty
+ manager.setKeymap({});
+ expect(manager.getAllShortcuts()).to.be.empty;
- manager.setKeymap(keymap)
- expect(manager.getAllShortcuts()).to.be.equal(keymap)
- })
+ manager.setKeymap(keymap);
+ expect(manager.getAllShortcuts()).to.be.equal(keymap);
+ });
it('should return all shortcuts for the Windows platform', () => {
- const manager = new ShortcutManager(keymap)
+ const manager = new ShortcutManager(keymap);
const keyMapResult = {
'Test': {
MOVE_LEFT: 'left',
@@ -58,14 +53,18 @@ describe('Shortcut manager', () => {
'OPEN': 'enter',
'CLOSE': 'esc',
},
- 'NON-EXISTING': {},
- }
+ 'PARENT': {
+ 'OPEN': 'enter',
+ 'NEXT': 'tab',
+ },
+ 'NO-SHORTCUTS': {},
+ };
- expect(manager.getAllShortcutsForPlatform('windows')).to.eql(keyMapResult)
- })
+ expect(manager.getAllShortcutsForPlatform('windows')).to.eql(keyMapResult);
+ });
it('should return all shortcuts for the macOs platform', () => {
- const manager = new ShortcutManager(keymap)
+ const manager = new ShortcutManager(keymap);
const keyMapResult = {
'Test': {
MOVE_LEFT: 'left',
@@ -82,89 +81,125 @@ describe('Shortcut manager', () => {
'OPEN': 'enter',
'CLOSE': 'esc',
},
- 'NON-EXISTING': {},
- }
+ 'PARENT': {
+ 'OPEN': 'enter',
+ 'NEXT': 'tab',
+ },
+ 'NO-SHORTCUTS': {},
+ };
- expect(manager.getAllShortcutsForPlatform('osx')).to.eql(keyMapResult)
- })
+ expect(manager.getAllShortcutsForPlatform('osx')).to.eql(keyMapResult);
+ });
it('should expose the change event type as a static constant', () =>
expect(ShortcutManager.CHANGE_EVENT).to.exist
- )
+ );
it('should have static CHANGE_EVENT', () =>
expect(ShortcutManager.CHANGE_EVENT).to.be.equal('shortcuts:update')
- )
+ );
it('should call onUpdate', () => {
- const manager = new ShortcutManager()
- const spy = sinon.spy()
- manager.addUpdateListener(spy)
- manager.setKeymap({})
- expect(spy).to.have.beenCalled
- })
+ const manager = new ShortcutManager();
+ const spy = sinon.spy();
+ manager.addUpdateListener(spy);
+ manager.setKeymap({});
+ expect(spy).to.have.been.called;
+ });
it('should throw an error when setKeymap is called without arg', () => {
- const manager = new ShortcutManager(keymap)
- const error = /setKeymap: keymap argument is not defined or falsy./
- expect(manager.setKeymap).to.throw(error)
- })
+ const manager = new ShortcutManager(keymap);
+ const error = /setKeymap: keymap argument is not defined or falsy./;
+ expect(manager.setKeymap).to.throw(error);
+ });
it('should extend the keymap', () => {
- const manager = new ShortcutManager()
- const newKeymap = { 'TESTING-NAMESPACE': {} }
- const extendedKeymap = Object.assign({}, keymap, newKeymap)
- manager.setKeymap(keymap)
- manager.extendKeymap(newKeymap)
+ const manager = new ShortcutManager();
+ const newKeymap = { 'TESTING-NAMESPACE': {} };
+ const extendedKeymap = Object.assign({}, keymap, newKeymap);
+ manager.setKeymap(keymap);
+ manager.extendKeymap(newKeymap);
- expect(manager.getAllShortcuts()).to.eql(extendedKeymap)
- })
+ expect(manager.getAllShortcuts()).to.eql(extendedKeymap);
+ });
it('should return array of shortcuts', () => {
- const manager = new ShortcutManager(keymap)
- let shortcuts = manager.getShortcuts('Test')
- expect(shortcuts).to.be.an.array
+ const manager = new ShortcutManager(keymap);
+ let shortcuts = manager.getShortcuts('Test');
+ expect(shortcuts).to.be.an('array');
- let shouldContainStrings = _.every(shortcuts, _.isString)
- expect(shouldContainStrings).to.be.equal(true)
- expect(shortcuts.length).to.be.equal(5)
+ let shouldContainStrings = _.every(shortcuts, _.isString);
+ expect(shouldContainStrings).to.be.equal(true);
+ expect(shortcuts.length).to.be.equal(5);
- shortcuts = manager.getShortcuts('Next')
- expect(shortcuts).to.be.an.array
- shouldContainStrings = _.every(shortcuts, _.isString)
- expect(shouldContainStrings).to.be.equal(true)
- expect(shortcuts.length).to.be.equal(5)
- })
+ shortcuts = manager.getShortcuts('Next');
+ expect(shortcuts).to.be.an('array');
+ shouldContainStrings = _.every(shortcuts, _.isString);
+ expect(shouldContainStrings).to.be.equal(true);
+ expect(shortcuts.length).to.be.equal(5);
+ });
it('should not throw an error when getting not existing key from keymap', () => {
- const manager = new ShortcutManager(keymap)
- const notExist = () => manager.getShortcuts('NotExist')
- expect(notExist).to.not.throw()
- })
+ const manager = new ShortcutManager(keymap);
+ const notExist = () => manager.getShortcuts('NotExist');
+ expect(notExist).to.not.throw();
+ });
it('should return correct key label', () => {
- const manager = new ShortcutManager()
- manager.setKeymap(keymap)
+ const manager = new ShortcutManager();
+ manager.setKeymap(keymap);
// Test
- expect(manager.findShortcutName('alt+backspace', 'Test')).to.be.equal('DELETE')
- expect(manager.findShortcutName('w', 'Test')).to.be.equal('MOVE_UP')
- expect(manager.findShortcutName('up', 'Test')).to.be.equal('MOVE_UP')
- expect(manager.findShortcutName('left', 'Test')).to.be.equal('MOVE_LEFT')
- expect(manager.findShortcutName('right', 'Test')).to.be.equal('MOVE_RIGHT')
+ expect(manager.findShortcutName('alt+backspace', 'Test')).to.be.equal('DELETE');
+ expect(manager.findShortcutName('w', 'Test')).to.be.equal('MOVE_UP');
+ expect(manager.findShortcutName('up', 'Test')).to.be.equal('MOVE_UP');
+ expect(manager.findShortcutName('left', 'Test')).to.be.equal('MOVE_LEFT');
+ expect(manager.findShortcutName('right', 'Test')).to.be.equal('MOVE_RIGHT');
// Next
- expect(manager.findShortcutName('alt+o', 'Next')).to.be.equal('OPEN')
- expect(manager.findShortcutName('d', 'Next')).to.be.equal('ABORT')
- expect(manager.findShortcutName('c', 'Next')).to.be.equal('ABORT')
- expect(manager.findShortcutName('esc', 'Next')).to.be.equal('CLOSE')
- expect(manager.findShortcutName('enter', 'Next')).to.be.equal('CLOSE')
- })
+ expect(manager.findShortcutName('alt+o', 'Next')).to.be.equal('OPEN');
+ expect(manager.findShortcutName('d', 'Next')).to.be.equal('ABORT');
+ expect(manager.findShortcutName('c', 'Next')).to.be.equal('ABORT');
+ expect(manager.findShortcutName('esc', 'Next')).to.be.equal('CLOSE');
+ expect(manager.findShortcutName('enter', 'Next')).to.be.equal('CLOSE');
+ });
it('should throw an error', () => {
- const manager = new ShortcutManager()
- const fn = () => manager.findShortcutName('left')
- expect(manager.findShortcutName).to.throw(/findShortcutName: keyName argument is not defined or falsy./)
- expect(fn).to.throw(/findShortcutName: componentName argument is not defined or falsy./)
- })
-})
+ const manager = new ShortcutManager();
+ const fn = () => manager.findShortcutName('left');
+ expect(manager.findShortcutName).to.throw(/findShortcutName: keyName argument is not defined or falsy./);
+ expect(fn).to.throw(/findShortcutName: componentName argument is not defined or falsy./);
+ });
+
+ context('when the keymap is updated', () => {
+ beforeEach(function () {
+ const { wrapper, node, context } = renderComponent();
+ node.focus();
+
+ this.node = node;
+ this.wrapper = wrapper;
+ this.shortcuts = context.shortcuts;
+ this.handler = wrapper.props().handler;
+ });
+
+ it('then uses the new keymap', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.SPACE });
+
+ expect(this.handler).to.not.have.been.called;
+
+ this.shortcuts.setKeymap({
+ ...keymap,
+ 'TESTING': {
+ 'SPACE': 'space',
+ },
+ });
+
+ setTimeout(() => {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.SPACE });
+ expect(this.handler).to.have.been.calledWith('SPACE');
+
+ this.shortcuts.setKeymap(keymap);
+ }, 100);
+ });
+ });
+});
diff --git a/test/shortcuts.alwaysFireHandler.spec.js b/test/shortcuts.alwaysFireHandler.spec.js
new file mode 100644
index 0000000..08da07e
--- /dev/null
+++ b/test/shortcuts.alwaysFireHandler.spec.js
@@ -0,0 +1,134 @@
+import React from 'react';
+
+import { expect } from 'chai';
+import simulant from 'simulant';
+
+import keymap from './keymap';
+import KeyCodes from './support/KeyCodes';
+import ShortcutManager from '../src/shortcut-manager';
+import renderComponent from './support/renderComponent';
+
+describe(' alwaysFireHandler prop:', () => {
+ beforeEach(function () {
+ const keymapWithPrintableChar = {
+ ...keymap,
+ 'TESTING': {
+ ...keymap.TESTING,
+ 'All': 'a',
+ },
+ };
+
+ const shortcutsManager = new ShortcutManager(keymapWithPrintableChar);
+ this.context = { shortcuts: shortcutsManager };
+ });
+
+ context('when no alwaysFireHandler value has been provided', () => {
+ beforeEach(function () {
+ const { wrapper, node } = renderComponent({
+ mergeWithBaseProps: {
+ children: ,
+ },
+ context: this.context,
+ });
+
+ this.wrapper = wrapper;
+ this.node = node;
+ });
+
+ it('then has a default value of false', () => {
+
+ });
+ context('and an input element is focused', () => {
+ beforeEach(function () {
+ this.childNode = this.node.querySelector('.input');
+ this.childNode.focus();
+ });
+
+ context('and a non-printable key matching a shortcut is pressed', () => {
+ it('then calls the handler with the correct arguments', function () {
+ simulant.fire(this.childNode, 'keydown', { keyCode: KeyCodes.ENTER, key: 'ENTER' });
+
+ expect(this.wrapper.props().handler).to.have.been.calledWith('OPEN');
+ });
+ });
+
+ context('and a non-printable key NOT matching a shortcut is pressed', () => {
+ it('then does NOT call the handler', function () {
+ simulant.fire(this.childNode, 'keydown', { keyCode: KeyCodes.TAB, key: 'TAB' });
+
+ expect(this.wrapper.props().handler).to.not.have.been.called;
+ });
+ });
+
+ context('and a printable key matching a shortcut is pressed', () => {
+ it('then does NOT calls the handler', function () {
+ simulant.fire(this.childNode, 'keydown', { keyCode: 65, key: 'a' });
+
+ expect(this.wrapper.props().handler).to.not.have.been.called;
+ });
+ });
+
+ context('and a printable key NOT matching a shortcut is pressed', () => {
+ it('then does NOT calls the handler', function () {
+ simulant.fire(this.childNode, 'keydown', { keyCode: 66, key: 'b' });
+
+ expect(this.wrapper.props().handler).to.not.have.been.called;
+ });
+ });
+ });
+ });
+
+ context('when a truthy alwaysFireHandler value has been provided', () => {
+ beforeEach(function () {
+ const { wrapper, node } = renderComponent({
+ mergeWithBaseProps: {
+ children: ,
+ alwaysFireHandler: true,
+ },
+ context: this.context,
+ });
+
+ this.wrapper = wrapper;
+ this.node = node;
+ });
+
+ context('and an input element is focused', () => {
+ beforeEach(function () {
+ this.childNode = this.node.querySelector('.input');
+ this.childNode.focus();
+ });
+
+ context('and a non-printable key matching a shortcut is pressed', () => {
+ it('then calls the handler with the correct arguments', function () {
+ simulant.fire(this.childNode, 'keydown', { keyCode: KeyCodes.ENTER, key: 'ENTER' });
+
+ expect(this.wrapper.props().handler).to.have.been.calledWith('OPEN');
+ });
+ });
+
+ context('and a non-printable key NOT matching a shortcut is pressed', () => {
+ it('then does NOT call the handler', function () {
+ simulant.fire(this.childNode, 'keydown', { keyCode: KeyCodes.TAB, key: 'TAB' });
+
+ expect(this.wrapper.props().handler).to.not.have.been.called;
+ });
+ });
+
+ context('and a printable key matching a shortcut is pressed', () => {
+ it('then does NOT calls the handler', function () {
+ simulant.fire(this.childNode, 'keydown', { keyCode: 65, key: 'a' });
+
+ expect(this.wrapper.props().handler).to.not.have.been.called;
+ });
+ });
+
+ context('and a printable key NOT matching a shortcut is pressed', () => {
+ it('then does NOT calls the handler', function () {
+ simulant.fire(this.childNode, 'keydown', { keyCode: 66, key: 'b' });
+
+ expect(this.wrapper.props().handler).to.not.have.been.called;
+ });
+ });
+ });
+ });
+});
diff --git a/test/shortcuts.global.spec.js b/test/shortcuts.global.spec.js
new file mode 100644
index 0000000..4e8a2ab
--- /dev/null
+++ b/test/shortcuts.global.spec.js
@@ -0,0 +1,177 @@
+import { expect } from 'chai';
+import simulant from 'simulant';
+
+import KeyCodes from './support/KeyCodes';
+import renderComponent from './support/renderComponent';
+
+describe(' global prop:', () => {
+ context('when no global value has been provided', () => {
+ beforeEach(function () {
+ const { wrapper, node } = renderComponent({ props: {} });
+ node.focus();
+
+ this.node = node;
+ this.wrapper = wrapper;
+ this.handler = wrapper.props().handler;
+ });
+
+ it('then has a value of false', function () {
+ expect(this.wrapper.props().global).to.be.equal(false);
+ });
+ });
+
+ context('when global is set to true', () => {
+ beforeEach(function () {
+ const { wrapper, node } = renderComponent({ mergeWithBaseProps: { global: true } });
+ node.focus();
+
+ this.node = node;
+ this.wrapper = wrapper;
+ this.handler = wrapper.props().handler;
+ });
+
+ context('and component does NOT contain a as a child', () => {
+ context('and a matching key is pressed', () => {
+ it('then calls the handler once with the correct arguments', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+
+ expect(this.handler).to.have.been.calledOnce;
+ expect(this.handler).to.have.been.calledWith('OPEN');
+ });
+ });
+
+ context('and a key that does NOT match any shortcuts is pressed', () => {
+ it('then does NOT call the handler', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.TAB });
+
+ expect(this.handler).to.not.have.been.called;
+ });
+ });
+
+ context('and the component has been unmounted', () => {
+ it('then does NOT call the handler when a matching key is pressed', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+ expect(this.handler).to.have.been.calledOnce;
+
+ this.wrapper.unmount();
+
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+ expect(this.handler).to.have.been.calledOnce;
+ });
+ });
+ });
+
+ context('and the contains another (NON-global) as a child, with overlapping shortcuts', () => {
+ beforeEach(function () {
+ const { wrapper: childWrapper, component: childComponent } = renderComponent({
+ mergeWithBaseProps: {
+ className: 'test',
+ },
+ });
+
+ const { wrapper: parentWrapper, node: parentNode } = renderComponent({ mergeWithBaseProps: {
+ children: childComponent,
+ name: 'PARENT',
+ global: true,
+ } });
+
+ const node = parentNode.querySelector('.test');
+ node.focus();
+
+ this.node = node;
+ this.childWrapper = childWrapper;
+ this.parentWrapper = parentWrapper;
+ });
+
+ context('and a key corresponding to a shortcut only in the child is pressed', () => {
+ it('then the parent Shortcuts\' handler is NOT called', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ESC });
+
+ expect(this.childWrapper.props().handler).to.have.been.calledWith('CLOSE');
+ expect(this.parentWrapper.props().handler).to.not.have.been.called;
+ });
+ });
+
+ context('and a key corresponding to a shortcut only in the parent is pressed', () => {
+ it('then the parent Shortcuts\' handler is called with the correct arguments', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.TAB });
+
+ expect(this.childWrapper.props().handler).to.not.have.been.called;
+ expect(this.parentWrapper.props().handler).to.have.been.calledWith('NEXT');
+ });
+ });
+
+ context('and a key corresponding to a shortcut both in the child and parent is pressed', () => {
+ beforeEach(function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+ });
+
+ it('then the parent Shortcuts\' handler is called with the correct arguments', function () {
+ expect(this.childWrapper.props().handler).to.have.been.calledWith('OPEN');
+ expect(this.parentWrapper.props().handler).to.have.been.calledWith('OPEN');
+ });
+
+ it('then calls the parent handler before the child handler', function () {
+ expect(this.parentWrapper.props().handler).to.have.been.calledBefore(this.childWrapper.props().handler);
+ });
+ });
+ });
+
+ context('and the contains another global as a child, with overlapping shortcuts', () => {
+ beforeEach(function () {
+ const { wrapper: childWrapper, component: childComponent } = renderComponent({
+ mergeWithBaseProps: {
+ className: 'test',
+ global: true,
+ },
+ });
+
+ const { wrapper: parentWrapper, node: parentNode } = renderComponent({ mergeWithBaseProps: {
+ children: childComponent,
+ name: 'PARENT',
+ global: true,
+ } });
+
+ const node = parentNode.querySelector('.test');
+ node.focus();
+
+ this.node = node;
+ this.childWrapper = childWrapper;
+ this.parentWrapper = parentWrapper;
+ });
+
+ context('and a key corresponding to a shortcut only in the child is pressed', () => {
+ it('then the parent Shortcuts\' handler is NOT called', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ESC });
+
+ expect(this.childWrapper.props().handler).to.have.been.calledWith('CLOSE');
+ expect(this.parentWrapper.props().handler).to.not.have.been.called;
+ });
+ });
+
+ context('and a key corresponding to a shortcut only in the parent is pressed', () => {
+ it('then the parent Shortcuts\' handler is called with the correct arguments', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.TAB });
+
+ expect(this.childWrapper.props().handler).to.not.have.been.called;
+ expect(this.parentWrapper.props().handler).to.have.been.calledWith('NEXT');
+ });
+ });
+
+ context('and a key corresponding to a shortcut both in the child and parent is pressed', () => {
+ beforeEach(function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+ });
+
+ it('then the parent Shortcuts\' handler is called with the correct arguments', function () {
+ expect(this.childWrapper.props().handler).to.have.been.calledWith('OPEN');
+ expect(this.parentWrapper.props().handler).to.have.been.calledWith('OPEN');
+ });
+
+ it('then calls the parent handler before the child handler', function () {
+ expect(this.parentWrapper.props().handler).to.have.been.calledBefore(this.childWrapper.props().handler);
+ });
+ });
+ });
+ });
+});
diff --git a/test/shortcuts.isolate.spec.js b/test/shortcuts.isolate.spec.js
new file mode 100644
index 0000000..3e5b0a2
--- /dev/null
+++ b/test/shortcuts.isolate.spec.js
@@ -0,0 +1,102 @@
+import { expect } from 'chai';
+import simulant from 'simulant';
+
+import KeyCodes from './support/KeyCodes';
+import renderComponent from './support/renderComponent';
+
+describe(' isolate prop:', () => {
+ context('when no isolate value has been provided', () => {
+ beforeEach(function () {
+ const { wrapper, node } = renderComponent({ props: {} });
+ node.focus();
+
+ this.node = node;
+ this.wrapper = wrapper;
+ this.handler = wrapper.props().handler;
+ });
+
+ it('then has a value of false', function () {
+ expect(this.wrapper.props().isolate).to.be.equal(false);
+ });
+ });
+
+ context('when isolate is set to true', () => {
+ context('and the component is rendered under another as a child', () => {
+ beforeEach(function () {
+ const { wrapper: childWrapper, component: childComponent } = renderComponent({
+ mergeWithBaseProps: {
+ className: 'test',
+ isolate: true,
+ },
+ });
+
+ const { wrapper: parentWrapper, node: parentNode } = renderComponent({ mergeWithBaseProps: {
+ children: childComponent,
+ name: 'PARENT',
+ } });
+
+ const node = parentNode.querySelector('.test');
+ node.focus();
+
+ this.node = node;
+ this.childWrapper = childWrapper;
+ this.parentWrapper = parentWrapper;
+ });
+
+ context('and a matching key is pressed', () => {
+ beforeEach(function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+ });
+
+ it('then calls the child handler once with the correct arguments', function () {
+ expect(this.childWrapper.props().handler).to.have.been.calledOnce;
+ expect(this.childWrapper.props().handler).to.have.been.calledWith('OPEN');
+ });
+
+ it('then DOES NOT call the parent handler', function () {
+ expect(this.parentWrapper.props().handler).to.not.have.been.called;
+ });
+ });
+ });
+
+ context('and the component is rendered under another (global) as a child', () => {
+ beforeEach(function () {
+ const { wrapper: childWrapper, component: childComponent } = renderComponent({
+ mergeWithBaseProps: {
+ className: 'test',
+ isolate: true,
+ },
+ });
+
+ const { wrapper: parentWrapper, node: parentNode } = renderComponent({ mergeWithBaseProps: {
+ children: childComponent,
+ name: 'PARENT',
+ global: true,
+ } });
+
+ const node = parentNode.querySelector('.test');
+ node.focus();
+
+ this.node = node;
+ this.childWrapper = childWrapper;
+ this.parentWrapper = parentWrapper;
+ });
+
+ context('and a matching key is pressed', () => {
+ beforeEach(function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+ });
+
+ it('then calls the child handler once with the correct arguments', function () {
+ expect(this.childWrapper.props().handler).to.have.been.calledOnce;
+ expect(this.childWrapper.props().handler).to.have.been.calledWith('OPEN');
+ });
+
+ it('then calls the parent handler once with the correct arguments', function () {
+ expect(this.parentWrapper.props().handler).to.have.been.calledOnce;
+ expect(this.parentWrapper.props().handler).to.have.been.calledWith('OPEN');
+ });
+ });
+ });
+ });
+});
diff --git a/test/shortcuts.props.spec.js b/test/shortcuts.props.spec.js
new file mode 100644
index 0000000..08b33fb
--- /dev/null
+++ b/test/shortcuts.props.spec.js
@@ -0,0 +1,105 @@
+import React from 'react';
+
+import { expect } from 'chai';
+
+import renderComponent from './support/renderComponent';
+
+describe(' props:', () => {
+ describe('when no overriding values are provided', () => {
+ beforeEach(function () {
+ const { wrapper } = renderComponent({ props: {} });
+ this.defaultProps = wrapper.props();
+ });
+
+ it('then tabIndex has a value of -1', function () {
+ expect(this.defaultProps.tabIndex).to.be.equal(-1);
+ });
+
+ it('then className has a value of null', function () {
+ expect(this.defaultProps.className).to.be.equal(null);
+ });
+
+ it('then isolate has a value of false', function () {
+ expect(this.defaultProps.isolate).to.be.equal(false);
+ });
+
+ it('then there are no children', function () {
+ expect(this.defaultProps.children).to.be.equal(undefined);
+ });
+
+ it('then eventType has a value of null', function () {
+ expect(this.defaultProps.eventType).to.be.equal(null);
+ });
+
+ it('then preventDefault has a value of false', function () {
+ expect(this.defaultProps.preventDefault).to.be.equal(false);
+ });
+
+ it('has no handler function', function () {
+ expect(this.defaultProps.handler).to.be.equal(undefined);
+ });
+ });
+
+ context('when overriding the default prop values', () => {
+ it('then renders positive tabIndex values correctly', () => {
+ const { wrapper } = renderComponent({ props: { tabIndex: 42 } });
+
+ expect(wrapper).to.have.attr('tabindex').equal('42');
+ });
+
+ it('then renders zero tabIndex values correctly', () => {
+ const { wrapper } = renderComponent({ props: { tabIndex: 0 } });
+
+ expect(wrapper).to.have.attr('tabindex').equal('0');
+ });
+
+ it('then renders className values correctly', () => {
+ const mergeWithBaseProps = { className: 'testing' };
+ const { wrapper } = renderComponent({ props: mergeWithBaseProps });
+
+ expect(wrapper).to.have.className(mergeWithBaseProps.className);
+ });
+
+ it('then renders children correctly', () => {
+ const mergeWithBaseProps = { children: };
+ const { wrapper } = renderComponent({ props: mergeWithBaseProps });
+
+ expect(wrapper).to.contain(mergeWithBaseProps.children);
+ });
+
+ it('then correctly sets the isolate value', () => {
+ const mergeWithBaseProps = { isolate: true };
+ const { wrapper } = renderComponent({ props: mergeWithBaseProps });
+
+ expect(wrapper.props().isolate).to.be.equal(true);
+ });
+
+ it('then correctly sets the handler value', () => {
+ const mergeWithBaseProps = { handler: () => {} };
+ const { wrapper } = renderComponent({ props: mergeWithBaseProps });
+
+ expect(wrapper.props().handler).to.be.equal(mergeWithBaseProps.handler);
+ });
+
+ it('then correctly sets the name value', () => {
+ const mergeWithBaseProps = { name: 'TESTING' };
+ const { wrapper } = renderComponent({ props: mergeWithBaseProps });
+
+ expect(wrapper.props().name).to.be.equal(mergeWithBaseProps.name);
+ });
+
+ it('then correctly sets the eventType value', () => {
+ const mergeWithBaseProps = { eventType: 'keyUp' };
+ const { wrapper } = renderComponent({ props: mergeWithBaseProps });
+
+ expect(wrapper.props().eventType).to.be.equal(mergeWithBaseProps.eventType);
+ });
+
+ it('then correctly sets preventDefault to true', () => {
+ const mergeWithBaseProps = { preventDefault: true };
+ const { wrapper } = renderComponent({ props: mergeWithBaseProps });
+
+ expect(wrapper.props().preventDefault).to.be.equal(true);
+ });
+ });
+});
diff --git a/test/shortcuts.spec.js b/test/shortcuts.spec.js
index a4bcd49..30ae56c 100644
--- a/test/shortcuts.spec.js
+++ b/test/shortcuts.spec.js
@@ -1,510 +1,111 @@
-import jsdom from 'jsdom'
-import chai from 'chai'
-import sinonChai from 'sinon-chai'
-import sinon from 'sinon'
-import _ from 'lodash'
+import React from 'react';
-import keymap from './keymap'
+import { expect } from 'chai';
+import simulant from 'simulant';
-describe('Shortcuts component', () => {
- let baseProps = null
- let baseContext = null
-
- let simulant = null
- let ShortcutManager = null
- let Shortcuts = null
- let ReactDOM = null
- let React = null
- let enzyme = null
-
- chai.use(sinonChai)
- const { expect } = chai
-
- beforeEach(() => {
- global.document = jsdom.jsdom('')
- global.window = document.defaultView
- global.Image = window.Image
- global.navigator = window.navigator
- global.CustomEvent = window.CustomEvent
- simulant = require('simulant')
- ReactDOM = require('react-dom')
- React = require('react')
- enzyme = require('enzyme')
- const chaiEnzyme = require('chai-enzyme')
-
- chai.use(chaiEnzyme())
-
- ShortcutManager = require('../src').ShortcutManager
- const shortcutsManager = new ShortcutManager(keymap)
-
- Shortcuts = require('../src/').Shortcuts
-
- baseProps = {
- handler: sinon.spy(),
- name: 'TESTING',
- className: null,
- }
- baseContext = { shortcuts: shortcutsManager }
- })
-
- it('should render component', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.find('div')).to.have.length(1)
- })
-
- it('should have a tabIndex of -1 by default', () => {
- let shortcutComponent = React.createElement(Shortcuts, baseProps)
- let wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().tabIndex).to.be.equal(-1)
-
- let props = _.assign({}, baseProps, { tabIndex: 42 })
- shortcutComponent = React.createElement(Shortcuts, props)
- wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().tabIndex).to.be.equal(props.tabIndex)
- let realTabIndex = ReactDOM.findDOMNode(wrapper.instance()).getAttribute('tabindex')
- expect(realTabIndex).to.have.equal(String(props.tabIndex))
-
- props.tabIndex = 0
- shortcutComponent = React.createElement(Shortcuts, props)
- wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().tabIndex).to.be.equal(props.tabIndex)
- realTabIndex = ReactDOM.findDOMNode(wrapper.instance()).getAttribute('tabindex')
- expect(realTabIndex).to.have.equal(String(props.tabIndex))
- })
-
- it('should not have className by default', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().className).to.be.equal(null)
- })
-
- it('should have className', () => {
- const props = _.assign({}, baseProps, { className: 'testing' })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().className).to.be.equal('testing')
- expect(wrapper).to.have.className('testing')
- })
-
- it('should have isolate prop set to false by default', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().isolate).to.be.equal(false)
- })
-
- it('should have isolate prop', () => {
- const props = _.assign({}, baseProps, { isolate: true })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().isolate).to.be.equal(true)
- })
-
- it('should not have children by default', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().children).to.be.equal(undefined)
- })
-
- it('should have children', () => {
- const props = _.assign({}, baseProps, { children: React.DOM.div() })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper).to.contain(React.DOM.div())
- })
-
- it('should have handler prop', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().handler).to.be.function
- })
-
- it('should have name prop', () => {
- const props = _.assign({}, baseProps,
- {name: 'TESTING'})
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().name).to.be.equal('TESTING')
- })
-
- it('should not have eventType prop by default', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().eventType).to.be.equal(null)
- })
-
- it('should have eventType prop', () => {
- const props = _.assign({}, baseProps, { eventType: 'keyUp' })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().eventType).to.be.equal('keyUp')
- })
-
- it('should have stopPropagation prop by default', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().stopPropagation).to.be.equal(true)
- })
-
- it('should have stopPropagation prop set to false', () => {
- const props = _.assign({}, baseProps, { stopPropagation: false })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().stopPropagation).to.be.equal(false)
- })
-
- it('should have preventDefault prop set to false by default', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().preventDefault).to.be.equal(false)
- })
-
- it('should have preventDefault prop set to true', () => {
- const props = _.assign({}, baseProps, { preventDefault: true })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().preventDefault).to.be.equal(true)
- })
-
- it('should not have targetNodeSelector prop by default', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().targetNodeSelector).to.be.equal(null)
- })
-
- it('should have targetNode prop', () => {
- const props = _.assign({}, baseProps, { targetNodeSelector: 'body' })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().targetNodeSelector).to.be.equal('body')
- })
-
- it('should have global prop set to false by default', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().global).to.be.equal(false)
- })
-
- it('should have global prop set to true', () => {
- const props = _.assign({}, baseProps, { global: true })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- expect(wrapper.props().global).to.be.equal(true)
- })
-
- it('should fire the handler prop with the correct argument', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const node = ReactDOM.findDOMNode(wrapper.instance())
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(wrapper.props().handler).to.have.been.calledWith('OPEN')
-
- const esc = 27
- simulant.fire(node, 'keydown', { keyCode: esc })
-
- expect(wrapper.props().handler).to.have.been.calledWith('CLOSE')
- })
-
- it('should not fire the handler', () => {
- const props = _.assign({}, baseProps, { name: 'NON-EXISTING' })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const node = ReactDOM.findDOMNode(wrapper.instance())
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(wrapper.props().handler).to.not.have.been.called
- })
-
- it('should not fire twice when global prop is truthy', () => {
- const props = _.assign({}, baseProps, { global: true })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const node = ReactDOM.findDOMNode(wrapper.instance())
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(wrapper.props().handler).to.have.been.calledOnce
- })
-
- it('should not fire when the component has been unmounted', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
+import KeyCodes from './support/KeyCodes';
+import renderComponent from './support/renderComponent';
- const node = ReactDOM.findDOMNode(wrapper.instance())
- node.focus()
-
- wrapper.unmount()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(wrapper.props().handler).to.not.have.been.called
- })
-
- it('should update the shortcuts and fire the handler', () => {
- const shortcutComponent = React.createElement(Shortcuts, baseProps)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const node = ReactDOM.findDOMNode(wrapper.instance())
- node.focus()
-
- const space = 32
- simulant.fire(node, 'keydown', { keyCode: space })
-
- expect(wrapper.props().handler).to.not.have.been.called
-
- const editedKeymap = _.assign({}, keymap, {
- 'TESTING': {
- 'SPACE': 'space',
- },
- }
- )
- baseContext.shortcuts.setKeymap(editedKeymap)
-
- simulant.fire(node, 'keydown', { keyCode: space })
-
- expect(baseProps.handler).to.have.been.called
-
- // NOTE: rollback the previous keymap
- baseContext.shortcuts.setKeymap(keymap)
- })
-
- it('should fire the handler from a child input', () => {
- const props = _.assign({}, baseProps, {
- children: React.DOM.input({ type: 'text', className: 'input' }),
- })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const parentNode = ReactDOM.findDOMNode(wrapper.instance())
- const node = parentNode.querySelector('.input')
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter, key: 'Enter' })
-
- expect(wrapper.props().handler).to.have.been.called
- })
-
- it('should fire the handler when using targetNodeSelector', () => {
- const props = _.assign({}, baseProps, { targetNodeSelector: 'body' })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const enter = 13
- simulant.fire(document.body, 'keydown', { keyCode: enter, key: 'Enter' })
-
- expect(wrapper.props().handler).to.have.been.called
- })
-
- it('should throw and error if targetNodeSelector is not found', () => {
- const props = _.assign({}, baseProps, { targetNodeSelector: 'non-existing' })
- const shortcutComponent = React.createElement(Shortcuts, props)
-
- try {
- enzyme.mount(shortcutComponent, { context: baseContext })
- } catch (err) {
- expect(err).to.match(/Node selector 'non-existing' was not found/)
- }
- })
-
- it('should fire the handler from focused input', () => {
- const props = _.assign({}, baseProps, {
- alwaysFireHandler: true,
- children: React.DOM.input({type: 'text', className: 'input'})
- })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const parentNode = ReactDOM.findDOMNode(wrapper.instance())
- const node = parentNode.querySelector('.input')
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(wrapper.props().handler).to.have.been.called
- })
-
-
- describe('Shortcuts component inside Shortcuts component:', () => {
-
- it('should not fire parent handler when child handler is fired', () => {
- const props = _.assign({}, baseProps, {
- children: React.createElement(Shortcuts, _.assign({}, baseProps, { className: 'test' })),
- })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const parentNode = ReactDOM.findDOMNode(wrapper.instance())
- const node = parentNode.querySelector('.test')
-
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(baseProps.handler).to.have.been.calledOnce
- })
-
- it('should fire parent handler when child handler is fired', () => {
- const props = _.assign({}, baseProps, {
- children: React.createElement(Shortcuts, _.assign({}, baseProps, { className: 'test', stopPropagation: false })),
- })
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const parentNode = ReactDOM.findDOMNode(wrapper.instance())
- const node = parentNode.querySelector('.test')
-
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(baseProps.handler).to.have.been.calledTwice
- })
-
- it('should fire parent handler when parent handler has global prop', () => {
- const props = _.assign({}, baseProps, {
- children: React.createElement(Shortcuts, _.assign({}, baseProps, { className: 'test' })),
- global: true,
- })
-
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const parentNode = ReactDOM.findDOMNode(wrapper.instance())
- const node = parentNode.querySelector('.test')
-
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(baseProps.handler).to.have.been.calledTwice
- })
-
- it('should fire parent handler but not the child handler', () => {
- const props = _.assign({}, baseProps, {
- children: React.createElement(Shortcuts, _.assign({}, baseProps, { name: 'NON-EXISTING', className: 'test' })),
- global: true,
- })
-
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const parentNode = ReactDOM.findDOMNode(wrapper.instance())
- const node = parentNode.querySelector('.test')
-
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(baseProps.handler).to.have.been.calledOnce
- })
-
- it('should fire for all global components', () => {
- const props = _.assign({}, baseProps, {
- children: React.createElement(Shortcuts, _.assign({}, baseProps, {
- global: true,
- children: React.createElement(Shortcuts, _.assign({}, baseProps, { name: 'NON-EXISTING', className: 'test' })),
- })),
- global: true,
- })
-
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const parentNode = ReactDOM.findDOMNode(wrapper.instance())
- const node = parentNode.querySelector('.test')
-
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(baseProps.handler).to.have.been.calledTwice
- })
-
- it('should not fire parent handler when a child has isolate prop set to true', () => {
- const childHandlerSpy = sinon.spy()
- const props = _.assign({}, baseProps, {
- children: React.createElement(Shortcuts, _.assign({}, baseProps, {
- className: 'test',
- isolate: true,
- handler: childHandlerSpy,
- })),
- })
-
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const parentNode = ReactDOM.findDOMNode(wrapper.instance())
- const node = parentNode.querySelector('.test')
-
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(childHandlerSpy).to.have.been.called
- expect(baseProps.handler).to.not.have.been.called
- })
-
- it('should fire parent handler when is global and a child has isolate prop set to true', () => {
- const props = _.assign({}, baseProps, {
- global: true,
- children: React.createElement(Shortcuts, _.assign({}, baseProps, { className: 'test', isolate: true })),
- })
-
- const shortcutComponent = React.createElement(Shortcuts, props)
- const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })
-
- const parentNode = ReactDOM.findDOMNode(wrapper.instance())
- const node = parentNode.querySelector('.test')
-
- node.focus()
-
- const enter = 13
- simulant.fire(node, 'keydown', { keyCode: enter })
-
- expect(baseProps.handler).to.have.been.called
- })
- })
-})
+describe('Shortcuts component', () => {
+ describe('Calling the handler function:', () => {
+ context('when an element in a namespace with defined shortcuts is focused', () => {
+ beforeEach(function () {
+ const { wrapper, node } = renderComponent();
+ node.focus();
+
+ this.node = node;
+ this.wrapper = wrapper;
+ this.handler = wrapper.props().handler;
+ });
+
+ context('and a matching key is pressed', () => {
+ it('then calls the handler with the correct action', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+ expect(this.handler).to.have.been.calledWith('OPEN');
+
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ESC });
+ expect(this.handler).to.have.been.calledWith('CLOSE');
+ });
+ });
+
+ context('and a key that doesn\'t match any shortcuts is pressed', () => {
+ it('then does NOT call the handler', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.TAB });
+
+ expect(this.handler).to.not.have.been.called;
+ });
+ });
+
+ context('and the component has been unmounted', () => {
+ it('then does not call the handler when a matching key is pressed', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+
+ expect(this.handler).to.have.been.calledOnce;
+
+ this.wrapper.unmount();
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+
+ expect(this.handler).to.have.been.calledOnce;
+ });
+ });
+ });
+
+ context('when an element in a namespace WITHOUT defined shortcuts is focused and a key is pressed', () => {
+ beforeEach(function () {
+ const { wrapper, node } = renderComponent({ mergeWithBaseProps: { name: 'NO-SHORTCUTS' } });
+ node.focus();
+
+ this.node = node;
+ this.wrapper = wrapper;
+ this.handler = wrapper.props().handler;
+ });
+
+ it('then does NOT call the handler', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+
+ expect(this.handler).to.not.have.been.called;
+ });
+ });
+
+ context('when an element in a namespace NOT defined in the keymap is focused', () => {
+ beforeEach(function () {
+ const { wrapper, node } = renderComponent({ mergeWithBaseProps: { name: 'NO-SHORTCUTS' } });
+ node.focus();
+
+ this.node = node;
+ this.wrapper = wrapper;
+ this.handler = wrapper.props().handler;
+ });
+
+ it('then does NOT call the handler', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+
+ expect(this.handler).to.not.have.been.called;
+ });
+ });
+ });
+
+ context('when a child element is focused and a key matching a shortcut is pressed', () => {
+ beforeEach(function () {
+ const { wrapper, node, context } = renderComponent({
+ mergeWithBaseProps: { children: },
+ });
+
+ node.focus();
+
+ const childNode = node.querySelector('.input');
+ childNode.focus();
+
+ this.childNode = childNode;
+ this.wrapper = wrapper;
+ this.shortcuts = context.shortcuts;
+ this.handler = wrapper.props().handler;
+ });
+
+ it('then calls the handler', function () {
+ simulant.fire(this.childNode, 'keydown', { keyCode: KeyCodes.ENTER, key: 'Enter' });
+
+ expect(this.handler).to.have.been.calledWith('OPEN');
+ });
+ });
+});
diff --git a/test/shortcuts.stopPropagation.spec.js b/test/shortcuts.stopPropagation.spec.js
new file mode 100644
index 0000000..0fa2c8a
--- /dev/null
+++ b/test/shortcuts.stopPropagation.spec.js
@@ -0,0 +1,122 @@
+import React from 'react';
+
+import { expect } from 'chai';
+import simulant from 'simulant';
+
+import KeyCodes from './support/KeyCodes';
+import renderComponent from './support/renderComponent';
+
+describe(' stopPropagation prop:', () => {
+ context('when no stopPropagation value has been provided', () => {
+ before(function () {
+ const { wrapper } = renderComponent({ props: {} });
+
+ this.wrapper = wrapper;
+ });
+
+ it('then has a value of null', function () {
+ expect(this.wrapper.props().stopPropagation).to.be.equal(true);
+ });
+
+ context('and the is rendered inside another with overlapping shortcuts', () => {
+ beforeEach(function () {
+ const { wrapper: childWrapper, component: childComponent } = renderComponent({
+ mergeWithBaseProps: {
+ className: 'test',
+ },
+ });
+
+ const { wrapper: parentWrapper, node: parentNode } = renderComponent({ mergeWithBaseProps: {
+ children: childComponent,
+ name: 'PARENT',
+ } });
+
+ const node = parentNode.querySelector('.test');
+ node.focus();
+
+ this.node = node;
+ this.childWrapper = childWrapper;
+ this.parentWrapper = parentWrapper;
+ });
+
+ context('and a key corresponding to a shortcut only in the child is pressed', () => {
+ it('then the parent Shortcuts\' handler is NOT called', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ESC });
+
+ expect(this.childWrapper.props().handler).to.have.been.calledWith('CLOSE');
+ expect(this.parentWrapper.props().handler).to.not.have.been.called;
+ });
+ });
+
+ context('and a key corresponding to a shortcut only in the parent is pressed', () => {
+ it('then the parent Shortcuts\' handler is NOT called', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.TAB });
+
+ expect(this.childWrapper.props().handler).to.not.have.been.called;
+ expect(this.parentWrapper.props().handler).to.not.have.been.called;
+ });
+ });
+
+ context('and a key corresponding to a shortcut both in the child and parent is pressed', () => {
+ it('then the parent Shortcuts\' handler is NOT called', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+
+ expect(this.childWrapper.props().handler).to.have.been.calledWith('OPEN');
+ expect(this.parentWrapper.props().handler).to.not.have.been.called;
+ });
+ });
+ });
+ });
+
+ context('when stopPropagation is set to false', () => {
+ context('and the is rendered inside another with overlapping shortcuts', () => {
+ beforeEach(function () {
+ const { wrapper: childWrapper, component: childComponent } = renderComponent({
+ mergeWithBaseProps: {
+ stopPropagation: false,
+ className: 'test',
+ },
+ });
+
+ const { wrapper: parentWrapper, node: parentNode } = renderComponent({ mergeWithBaseProps: {
+ children: childComponent,
+ name: 'PARENT',
+ } });
+
+ const node = parentNode.querySelector('.test');
+ node.focus();
+
+ this.node = node;
+ this.childWrapper = childWrapper;
+ this.parentWrapper = parentWrapper;
+ });
+
+ context('and a key corresponding to a shortcut only in the child is pressed', () => {
+ it('then the parent Shortcuts\' handler is NOT called', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ESC });
+
+ expect(this.childWrapper.props().handler).to.have.been.calledWith('CLOSE');
+ expect(this.parentWrapper.props().handler).to.not.have.been.called;
+ });
+ });
+
+ context('and a key corresponding to a shortcut only in the parent is pressed', () => {
+ it('then the parent Shortcuts\' handler is called with the correct arguments', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.TAB });
+
+ expect(this.childWrapper.props().handler).to.not.have.been.called;
+ expect(this.parentWrapper.props().handler).to.have.been.calledWith('NEXT');
+ });
+ });
+
+ context('and a key corresponding to a shortcut both in the child and parent is pressed', () => {
+ it('then the parent Shortcuts\' handler is called with the correct arguments', function () {
+ simulant.fire(this.node, 'keydown', { keyCode: KeyCodes.ENTER });
+
+ expect(this.childWrapper.props().handler).to.have.been.calledOnce;
+ expect(this.parentWrapper.props().handler).to.have.been.calledOnce;
+ });
+ });
+ });
+ });
+});
diff --git a/test/shortcuts.targetNodeSelector.spec.js b/test/shortcuts.targetNodeSelector.spec.js
new file mode 100644
index 0000000..0b1e33d
--- /dev/null
+++ b/test/shortcuts.targetNodeSelector.spec.js
@@ -0,0 +1,52 @@
+import React from 'react';
+
+import enzyme from 'enzyme';
+import { expect } from 'chai';
+import simulant from 'simulant';
+
+import KeyCodes from './support/KeyCodes';
+import renderComponent from './support/renderComponent';
+
+import Shortcuts from '../src/component/shortcuts';
+import ShortcutManager from '../src/shortcut-manager';
+
+describe(' targetNodeSelector prop:', () => {
+ context('when no alwaysFireHandler value has been provided', () => {
+ before(function () {
+ const { wrapper } = renderComponent({ props: {} });
+
+ this.wrapper = wrapper;
+ });
+
+ it('then has a value of null', function () {
+ expect(this.wrapper.props().targetNodeSelector).to.be.equal(null);
+ });
+ });
+
+ context('when provided a targetNodeSelector', () => {
+ context('that matches an element in the DOM', () => {
+ it('then calls the handler when a matching key is pressed', () => {
+ const { wrapper } = renderComponent({ mergeWithBaseProps: { targetNodeSelector: 'body' } });
+
+ simulant.fire(document.body, 'keydown', { keyCode: KeyCodes.ENTER, key: 'Enter' });
+
+ expect(wrapper.props().handler).to.have.been.calledWith('OPEN');
+ });
+ });
+
+ context('that does NOT match an element in the DOM', () => {
+ it('then calls the handler when a matching key is pressed', () => {
+ const component = (
+
+ );
+
+ const shortcutsManager = new ShortcutManager({});
+ const context = { shortcuts: shortcutsManager };
+
+ expect(() => enzyme.mount(component, { context })).to.throw(
+ 'Node selector \'non-existent\' was not found.'
+ );
+ });
+ });
+ });
+});
diff --git a/test/support/KeyCodes.js b/test/support/KeyCodes.js
new file mode 100644
index 0000000..02026a1
--- /dev/null
+++ b/test/support/KeyCodes.js
@@ -0,0 +1,6 @@
+export default {
+ ENTER: 13,
+ ESC: 27,
+ TAB: 9,
+ SPACE: 32,
+};
diff --git a/test/support/renderComponent.js b/test/support/renderComponent.js
new file mode 100644
index 0000000..e1fa9b6
--- /dev/null
+++ b/test/support/renderComponent.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import enzyme from 'enzyme';
+
+import sinon from 'sinon';
+
+import keymap from '../keymap';
+import Shortcuts from '../../src/component/shortcuts';
+import ShortcutManager from '../../src/shortcut-manager';
+
+function renderComponent({ props, mergeWithBaseProps, context } = {}) {
+ const effectiveProps = (() => {
+ if (props) {
+ return props;
+ }
+
+ const baseProps = {
+ handler: sinon.spy(),
+ name: 'TESTING',
+ className: null,
+ };
+
+ if (mergeWithBaseProps) {
+ return { ...baseProps, ...mergeWithBaseProps };
+ }
+
+ return baseProps;
+ })();
+
+ const component = (
+
+ );
+
+ const effectiveContext = (() => {
+ if (context) {
+ return context;
+ }
+
+ const shortcutsManager = new ShortcutManager(keymap);
+ return { shortcuts: shortcutsManager };
+ })();
+
+ const wrapper = enzyme.mount(component, { context: effectiveContext });
+ const node = ReactDOM.findDOMNode(wrapper.instance());
+
+ return {
+ wrapper, component, node, context: effectiveContext,
+ };
+}
+
+export default renderComponent;
diff --git a/test/utils.js b/test/utils.js
index 160873e..9400c5d 100644
--- a/test/utils.js
+++ b/test/utils.js
@@ -1,13 +1,13 @@
-import chai from 'chai'
-import _ from 'lodash'
-import { isArray, isPlainObject, findKey, compact, flatten, map } from '../src/utils'
+import chai from 'chai';
+import _ from 'lodash';
+import { isArray, isPlainObject, findKey, compact, flatten, map } from '../src/utils';
describe('utils', () => {
- const { expect } = chai
- let primitives
+ const { expect } = chai;
+ let primitives;
beforeEach(() => {
- function fn() { this.a = 1 }
+ function fn() { this.a = 1; }
primitives = [
['array'],
@@ -17,40 +17,40 @@ describe('utils', () => {
null,
undefined,
NaN,
- new Map([[ 1, 'one' ], [ 2, 'two' ]]),
+ new Map([[1, 'one'], [2, 'two']]),
new fn(),
true,
42,
- ]
- })
+ ];
+ });
describe('isArray', () => {
it('should be true for arrays', () => {
primitives.forEach((val, idx) => {
if (idx === 0) {
- expect(isArray(val)).to.be.true
- expect(_.isArray(val)).to.be.true
+ expect(isArray(val)).to.be.true;
+ expect(_.isArray(val)).to.be.true;
} else {
- expect(isArray(val)).to.be.false
- expect(_.isArray(val)).to.be.false
+ expect(isArray(val)).to.be.false;
+ expect(_.isArray(val)).to.be.false;
}
- })
- })
- })
+ });
+ });
+ });
describe('isPlainObject', () => {
it('should be true for plain objects', () => {
primitives.forEach((val, idx) => {
if (idx === 1 || idx === 2) {
- expect(isPlainObject(val)).to.be.true
- expect(_.isPlainObject(val)).to.be.true
+ expect(isPlainObject(val)).to.be.true;
+ expect(_.isPlainObject(val)).to.be.true;
} else {
- expect(isPlainObject(val)).to.be.false
- expect(_.isPlainObject(val)).to.be.false
+ expect(isPlainObject(val)).to.be.false;
+ expect(_.isPlainObject(val)).to.be.false;
}
- })
- })
- })
+ });
+ });
+ });
describe('findKey', () => {
it('should return the matching key', () => {
@@ -59,15 +59,15 @@ describe('utils', () => {
obj: {
val: 4,
},
- }
+ };
- const checkOne = val => val === 1
- const checkTwo = val => typeof val === 'object'
+ const checkOne = val => val === 1;
+ const checkTwo = val => typeof val === 'object';
- expect(findKey(obj, checkOne)).to.deep.equal(_.findKey(obj, checkOne))
- expect(findKey(obj, checkTwo)).to.deep.equal(_.findKey(obj, checkTwo))
- })
- })
+ expect(findKey(obj, checkOne)).to.deep.equal(_.findKey(obj, checkOne));
+ expect(findKey(obj, checkTwo)).to.deep.equal(_.findKey(obj, checkTwo));
+ });
+ });
describe('compact', () => {
it('removes falsy values', () => {
@@ -81,52 +81,52 @@ describe('utils', () => {
NaN,
'',
'false, null, 0, "", undefined, and NaN are falsy',
- ]
+ ];
- expect(compact(values)).to.deep.equal(_.compact(values))
- })
- })
+ expect(compact(values)).to.deep.equal(_.compact(values));
+ });
+ });
describe('flatten', () => {
it('flattens an array 1 level', () => {
- const value = [1, [2, [3, [4]], 5, [[[6], 7], 8], 9]]
- expect(flatten(value)).to.deep.equal(_.flatten(value))
- })
- })
+ const value = [1, [2, [3, [4]], 5, [[[6], 7], 8], 9]];
+ expect(flatten(value)).to.deep.equal(_.flatten(value));
+ });
+ });
describe('map', () => {
it('should map an array', () => {
- const values = [1, 2, 3, 4]
- const mapFn = val => val * 10
+ const values = [1, 2, 3, 4];
+ const mapFn = val => val * 10;
- expect(map(values, mapFn)).to.deep.equal(_.map(values, mapFn))
- expect(map(values, mapFn)).to.deep.equal([10, 20, 30, 40])
+ expect(map(values, mapFn)).to.deep.equal(_.map(values, mapFn));
+ expect(map(values, mapFn)).to.deep.equal([10, 20, 30, 40]);
// ensure that values array is not mutated
- expect(values).to.deep.equal([1, 2, 3, 4])
- })
+ expect(values).to.deep.equal([1, 2, 3, 4]);
+ });
it('should map an object', () => {
const obj = {
one: 1,
two: 2,
three: 3,
- }
- const mapFn = (val, key) => `${key} - ${val * 10}`
+ };
+ const mapFn = (val, key) => `${key} - ${val * 10}`;
- expect(map(obj, mapFn)).to.deep.equal(_.map(obj, mapFn))
+ expect(map(obj, mapFn)).to.deep.equal(_.map(obj, mapFn));
expect(map(obj, mapFn)).to.deep.equal([
'one - 10',
'two - 20',
'three - 30',
- ])
+ ]);
// ensure the object was not mutated
expect(obj).to.deep.equal({
one: 1,
two: 2,
three: 3,
- })
- })
- })
-})
+ });
+ });
+ });
+});