diff --git a/.babelrc b/.babelrc
index c13c5f6..05dcf49 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,3 +1,11 @@
{
- "presets": ["es2015"]
+ "presets": [
+ ["es2015", {"modules": false}]
+ ],
+
+ "env": {
+ "test": {
+ "plugins": ["transform-es2015-modules-commonjs"]
+ }
+ }
}
diff --git a/.eslintrc b/.eslintrc
index 1343963..3763bfd 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,11 +1,15 @@
{
"env": {
+ "jest/globals": true,
"browser": true
},
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
+ "plugins": [
+ "jest"
+ ],
"rules": {
"no-cond-assign": 0,
"no-console": 2,
diff --git a/package.json b/package.json
index 6248335..c75ab80 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"ava": "^0.19.1",
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
+ "babel-jest": "^19.0.0",
"babel-loader": "^6.4.1",
"babel-preset-env": "^1.4.0",
"babel-preset-es2015": "^6.24.1",
@@ -38,8 +39,11 @@
"browser-env": "^2.0.30",
"eslint": "^3.19.0",
"eslint-loader": "^1.7.1",
+ "eslint-plugin-jest": "^19.0.1",
+ "jest": "^19.0.2",
"npm-run-all": "^4.0.2",
"rimraf": "^2.6.1",
+ "simulant": "^0.2.2",
"smart-banner-webpack-plugin": "^3.0.1",
"webpack": "^2.4.1",
"webpack-dev-server": "^2.4.2"
@@ -50,31 +54,6 @@
"build:main": "webpack",
"build:main.min": "webpack --output-filename [name].min.js -p",
"dev": "webpack-dev-server",
- "test": "ava test/*.js"
- },
- "babel": {
- "presets": [
- [
- "es2015",
- {
- "modules": false
- },
- "@ava/stage-4",
- "@ava/transform-test-files"
- ]
- ]
- },
- "ava": {
- "babel": {
- "presets": [
- "es2015",
- "stage-0",
- "react"
- ]
- },
- "require": [
- "babel-register",
- "./test/helpers/setup-browser-env.js"
- ]
+ "test": "jest"
}
}
diff --git a/src/js/utils/dom.js b/src/js/utils/dom.js
index 181ddf3..ff1ee77 100644
--- a/src/js/utils/dom.js
+++ b/src/js/utils/dom.js
@@ -46,14 +46,17 @@ export default class DOM {
/**
* Gets the prefixed transitionend event.
+ * @param {?Element} optEl Element to check
* @return {string}
*/
- static getTransitionEvent() {
- if (transitionEvent) {
+ static getTransitionEvent(optEl) {
+ if (transitionEvent && !optEl) {
return transitionEvent;
}
- const el = document.createElement('ws');
+ transitionEvent = '';
+
+ const el = optEl || document.createElement('ws');
const transitions = {
'transition': 'transitionend',
'OTransition': 'oTransitionEnd',
@@ -76,14 +79,17 @@ export default class DOM {
/**
* Gets the prefixed animation end event.
+ * @param {?Element} optEl Element to check
* @return {string}
*/
- static getAnimationEvent() {
- if (animationEvent) {
+ static getAnimationEvent(optEl) {
+ if (animationEvent && !optEl) {
return animationEvent;
}
- const el = document.createElement('ws');
+ animationEvent = '';
+
+ const el = optEl || document.createElement('ws');
const animations = {
'animation': 'animationend',
'OAnimation': 'oAnimationEnd',
@@ -156,10 +162,9 @@ export default class DOM {
if (document.activeElement) {
const isContentEditable = document.activeElement
- .contentEditable !== 'inherit';
+ .contentEditable !== 'inherit' && document.activeElement.contentEditable !== undefined;
const isInput = ['INPUT', 'SELECT', 'OPTION', 'TEXTAREA']
.indexOf(document.activeElement.tagName) > -1;
-
result = isInput || isContentEditable;
}
diff --git a/test/utils/dom.test.js b/test/utils/dom.test.js
new file mode 100644
index 0000000..53bf72d
--- /dev/null
+++ b/test/utils/dom.test.js
@@ -0,0 +1,270 @@
+import DOM from '../../src/js/utils/dom';
+import simulant from 'simulant';
+
+describe('Node creation', () => {
+ test('Creates a node', () => {
+ const node = DOM.createNode('p');
+
+ expect(node).toBeInstanceOf(Element);
+ expect(node.tagName).toBe('P');
+ expect(node.id).toBe('');
+ });
+
+ test('Should be possible to pass an id', () => {
+ const node = DOM.createNode('p', 'myId');
+
+ expect(node.id).toBe('myId');
+ });
+
+ test('Should be possible to pass text', () => {
+ const node = DOM.createNode('p', 'id', 'foo');
+
+ expect(node.textContent).toBe('foo');
+ });
+});
+
+describe('Once', () => {
+ let parent;
+ let inner;
+
+ beforeEach(() => {
+ document.body.innerHTML = `
+
+ `;
+ parent = document.getElementById('parent');
+ inner = document.getElementById('inner');
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = '';
+ });
+
+ test('Only once called once', () => {
+ const cb = jest.fn();
+ DOM.once(parent, 'click', cb);
+ simulant.fire(parent, 'click');
+ simulant.fire(parent, 'click');
+ simulant.fire(parent, 'click');
+
+ expect(cb).toHaveBeenCalledTimes(1);
+ });
+
+ test('Callback doesn\'t run on bubbled event', () => {
+ const cb = jest.fn();
+ DOM.once(parent, 'click', cb);
+ simulant.fire(inner, 'click');
+
+ expect(cb).not.toHaveBeenCalled();
+ });
+});
+
+describe('Transition', () => {
+ test('Returns unprefixed first if available', () => {
+ const fakeEl = {
+ style: {
+ transition: 'foo',
+ OTransition: 'foo',
+ MozTransition: 'foo',
+ WebkitTransition: 'foo'
+ }
+ };
+
+ expect(DOM.getTransitionEvent(fakeEl)).toBe('transitionend');
+ });
+
+ test('Prefixed Opera', () => {
+ const fakeEl = {
+ style: {
+ OTransition: 'foo'
+ }
+ };
+
+ expect(DOM.getTransitionEvent(fakeEl)).toBe('oTransitionEnd');
+ });
+
+
+ test('Prefixed Gecko', () => {
+ const fakeEl = {
+ style: {
+ MozTransition: 'foo'
+ }
+ };
+
+ expect(DOM.getTransitionEvent(fakeEl)).toBe('transitionend');
+ });
+
+
+ test('Prefixed Webkit', () => {
+ const fakeEl = {
+ style: {
+ WebkitTransition: 'foo'
+ }
+ };
+
+ expect(DOM.getTransitionEvent(fakeEl)).toBe('webkitTransitionEnd');
+ });
+
+ test('Retains value', () => {
+ const fakeEl = {
+ style: {
+ WebkitTransition: 'foo'
+ }
+ };
+
+ expect(DOM.getTransitionEvent(fakeEl)).toBe('webkitTransitionEnd');
+ expect(DOM.getTransitionEvent()).toBe('webkitTransitionEnd');
+ });
+});
+
+describe('Animation', () => {
+ test('Returns unprefixed first if available', () => {
+ const fakeEl = {
+ style: {
+ animation: 'foo',
+ OAnimation: 'foo',
+ MozAnimation: 'foo',
+ WebkitAnimation: 'foo'
+ }
+ };
+
+ expect(DOM.getAnimationEvent(fakeEl)).toBe('animationend');
+ });
+
+ test('Prefixed Opera', () => {
+ const fakeEl = {
+ style: {
+ OAnimation: 'foo'
+ }
+ };
+
+ expect(DOM.getAnimationEvent(fakeEl)).toBe('oAnimationEnd');
+ });
+
+
+ test('Prefixed Gecko', () => {
+ const fakeEl = {
+ style: {
+ MozAnimation: 'foo'
+ }
+ };
+
+ expect(DOM.getAnimationEvent(fakeEl)).toBe('animationend');
+ });
+
+
+ test('Prefixed Webkit', () => {
+ const fakeEl = {
+ style: {
+ WebkitAnimation: 'foo'
+ }
+ };
+
+ expect(DOM.getAnimationEvent(fakeEl)).toBe('webkitAnimationEnd');
+ });
+
+ test('Retains value', () => {
+ const fakeEl = {
+ style: {
+ WebkitAnimation: 'foo'
+ }
+ };
+
+ expect(DOM.getAnimationEvent(fakeEl)).toBe('webkitAnimationEnd');
+ expect(DOM.getAnimationEvent()).toBe('webkitAnimationEnd');
+ });
+});
+
+describe('Show/hide', () => {
+ test('Show removes the display property', () => {
+ const el = DOM.createNode('div');
+ el.style.display = 'flex';
+
+ expect(el.style.display).toBe('flex');
+ DOM.show(el);
+ expect(el.style.display).toBe('');
+ });
+
+ test('Hide adds display none', () => {
+ const el = DOM.createNode('div');
+
+ expect(el.style.display).toBe('');
+ DOM.hide(el);
+ expect(el.style.display).toBe('none');
+ });
+});
+
+describe('Custom Event', () => {
+ test('Event gets fired', () => {
+ const cb = jest.fn();
+ const el = DOM.createNode('div');
+
+ el.addEventListener('foo', cb);
+ DOM.fireEvent(el, 'foo');
+ expect(cb).toHaveBeenCalled();
+ });
+
+ test('Event can pass data', () => {
+ const cb = jest.fn();
+ const el = DOM.createNode('div');
+
+ el.addEventListener('foo', cb);
+ DOM.fireEvent(el, 'foo', {
+ foo: 'bar'
+ });
+ expect(cb.mock.calls[0][0].detail.foo).toBe('bar');
+ });
+});
+
+describe('To Array', () => {
+ test('Converts to array', () => {
+ document.body.innerHTML = '';
+ const paragraphs = document.querySelectorAll('p');
+
+ expect(paragraphs.length).toBe(5);
+ expect(paragraphs).not.toBeInstanceOf(Array);
+ expect(DOM.toArray(paragraphs)).toBeInstanceOf(Array);
+ expect(DOM.toArray(paragraphs).length).toBe(5);
+
+ document.body.innerHTML = '';
+ });
+});
+
+describe('Focusble Element', () => {
+ beforeEach(() => {
+ document.body.innerHTML = `
+
+
+
+
+ `;
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = '';
+ });
+
+ test('Returns false if not focusable', () => {
+ document.getElementById('noContent').focus();
+ expect(DOM.isFocusableElement()).toBe(false);
+ });
+
+ test('Returns true if focusable', () => {
+ document.getElementById('noContent').focus();
+ expect(DOM.isFocusableElement()).toBe(false);
+ document.getElementById('input').focus();
+ expect(DOM.isFocusableElement()).toBe(true);
+ document.getElementById('noContent').focus();
+ expect(DOM.isFocusableElement()).toBe(false);
+ document.getElementById('select').focus();
+ expect(DOM.isFocusableElement()).toBe(true);
+ document.getElementById('noContent').focus();
+ expect(DOM.isFocusableElement()).toBe(false);
+ document.getElementById('textarea').focus();
+ expect(DOM.isFocusableElement()).toBe(true);
+ });
+});
diff --git a/test/utils/keys.test.js b/test/utils/keys.test.js
new file mode 100644
index 0000000..9482ced
--- /dev/null
+++ b/test/utils/keys.test.js
@@ -0,0 +1,14 @@
+import Keys from '../../src/js/utils/keys';
+
+test('Keys are present', () => {
+ expect(Keys.ENTER).toBe(13);
+ expect(Keys.SPACE).toBe(32);
+ expect(Keys.RE_PAGE).toBe(33);
+ expect(Keys.AV_PAGE).toBe(34);
+ expect(Keys.END).toBe(35);
+ expect(Keys.HOME).toBe(36);
+ expect(Keys.LEFT).toBe(37);
+ expect(Keys.UP).toBe(38);
+ expect(Keys.RIGHT).toBe(39);
+ expect(Keys.DOWN).toBe(40);
+});