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); +});