From f286dbc653f62c2cddeba31bf2be3b6618c416db Mon Sep 17 00:00:00 2001 From: Josh Johnson Date: Mon, 28 May 2018 17:56:36 +0100 Subject: [PATCH] Add util tests --- .eslintrc | 1 + config/test.js | 5 +- package.json | 5 +- src/scripts/lib/utils.js | 49 ++--- src/scripts/lib/utils.test.js | 280 ++++++++++++++++++++++++++++- src/scripts/reducers/index.test.js | 39 ++++ 6 files changed, 349 insertions(+), 30 deletions(-) diff --git a/.eslintrc b/.eslintrc index e57c66b..454b853 100644 --- a/.eslintrc +++ b/.eslintrc @@ -26,6 +26,7 @@ "devDependencies": true }], "no-console": ["warn", { "allow": ["warn", "error"] }], + "no-plusplus": "off", "no-unused-expressions": "off", "no-underscore-dangle": "off", "prettier/prettier": ["error", { diff --git a/config/test.js b/config/test.js index ff9bd08..b1e172d 100644 --- a/config/test.js +++ b/config/test.js @@ -15,7 +15,7 @@ function ignoreExtensions(extensions = [], returnValue = {}) { return returnValue; } - extensions.forEach((ext) => { + extensions.forEach(ext => { require.extensions[ext] = noop; }); } @@ -35,7 +35,7 @@ function mockRAF(global) { global.requestAnimationFrame = callback => callbacksQueue.push(callback) - 1; - global.cancelAnimationFrame = (id) => { + global.cancelAnimationFrame = id => { callbacksQueue[id] = false; }; } @@ -46,6 +46,7 @@ global.navigator = { userAgent: 'node.js', }; global.CustomEvent = window.CustomEvent; +global.Element = window.Element; global.HTMLElement = window.HTMLElement; global.HTMLOptionElement = window.HTMLOptionElement; global.HTMLOptGroupElement = window.HTMLOptGroupElement; diff --git a/package.json b/package.json index 9c8e667..e190137 100644 --- a/package.json +++ b/package.json @@ -86,11 +86,10 @@ ], "nyc": { "include": [ - "src/**/**/**/**/**/*.js" + "src/scripts/**/**/*.js" ], "exclude": [ - "src/**/**/**/**/**/*.test.js", - "src/scripts/src/lib/polyfills.js" + "src/scripts/**/**/*.test.js" ] } } diff --git a/src/scripts/lib/utils.js b/src/scripts/lib/utils.js index 06e59de..1a93991 100644 --- a/src/scripts/lib/utils.js +++ b/src/scripts/lib/utils.js @@ -38,15 +38,15 @@ export const generateId = function(element, prefix) { (element.name && `${element.name}-${generateChars(2)}`) || generateChars(4); id = id.replace(/(:|\.|\[|\]|,)/g, ''); - id = prefix + id; + id = `${prefix}-${id}`; return id; }; /** - * Tests the type of an object - * @param {String} type Type to test object against - * @param {Object} obj Object to be tested + * Gets the type of an object + * Why not use typeof? See here: http: //bonsaiden.github.io/JavaScript-Garden/#types.typeof + * @param {Object} obj Object to check * @return {Boolean} */ export const getType = function(obj) { @@ -69,14 +69,9 @@ export const isType = function(type, obj) { * @param {Object} obj Object to be tested * @return {Boolean} */ -export const isElement = o => - typeof HTMLElement === 'object' - ? o instanceof HTMLElement // DOM2 - : o && - typeof o === 'object' && - o !== null && - o.nodeType === 1 && - typeof o.nodeName === 'string'; +export const isElement = (element) => { + return element instanceof Element; +}; /** * Merges unspecified amount of objects into new object @@ -294,8 +289,14 @@ export const sortByAlpha = (a, b) => { const labelA = (a.label || a.value).toLowerCase(); const labelB = (b.label || b.value).toLowerCase(); - if (labelA < labelB) return -1; - if (labelA > labelB) return 1; + if (labelA < labelB) { + return -1; + } + + if (labelA > labelB) { + return 1; + } + return 0; }; @@ -364,20 +365,20 @@ export const reduceToValues = (items, key = 'value') => { /** * Fetch properties from object - * @param {Object} object Related object - * @param {String} properties Properties from object + * @param {Object} object Related object + * @param {String} path Path to value */ -export const fetchFromObject = (object, properties) => { - const index = properties.indexOf('.'); +export const fetchFromObject = (object, path) => { + const index = path.indexOf('.'); if (index > -1) { return fetchFromObject( - object[properties.substring(0, index)], - properties.substr(index + 1), + object[path.substring(0, index)], + path.substr(index + 1), ); } - return object[properties]; + return object[path]; }; export const isIE11 = () => @@ -386,13 +387,13 @@ export const isIE11 = () => navigator.userAgent.match(/rv[ :]11/) ); -export const existsInArray = (array, value) => +export const existsInArray = (array, value, key = 'value') => array.some(item => { if (isType('String', value)) { - return item.value === value.trim(); + return item[key] === value.trim(); } - return item.value === value; + return item[key] === value; }); /** diff --git a/src/scripts/lib/utils.test.js b/src/scripts/lib/utils.test.js index a0036e2..1e41652 100644 --- a/src/scripts/lib/utils.test.js +++ b/src/scripts/lib/utils.test.js @@ -1,5 +1,22 @@ import { expect } from 'chai'; -import { reduceToValues } from './utils'; +import { stub } from 'sinon'; +import { + reduceToValues, + getRandomNumber, + generateChars, + generateId, + getType, + isType, + isElement, + stripHTML, + sortByAlpha, + sortByScore, + fetchFromObject, + existsInArray, + cloneObject, + regexFilter, + dispatchEvent, +} from './utils'; describe('utils', () => { describe('reduceToValues', () => { @@ -49,4 +66,265 @@ describe('utils', () => { expect(actualResponse).to.eql(expectedResponse); }); }); + + describe('getRandomNumber', () => { + it('returns random number between range', () => { + for (let index = 0; index < 10; index++) { + const output = getRandomNumber(1, 10); + expect(output).to.be.a('number'); + expect(output).to.be.within(1, 10); + } + }); + }); + + describe('generateChars', () => { + it('generates a string of random chars with given length', () => { + const output = generateChars(10); + expect(output).to.be.a('string'); + expect(output).to.have.length(10); + }); + }); + + describe('generateId', () => { + describe('when given element has id value', () => { + it('generates a unique prefixed id based on given elements id', () => { + const element = document.createElement('div'); + element.id = 'test-id'; + const prefix = 'test-prefix'; + + const output = generateId(element, prefix); + + expect(output).to.equal(`${prefix}-${element.id}`); + }); + }); + + describe('when given element has no id value but name value', () => { + it('generates a unique prefixed id based on given elements name plus 2 random characters', () => { + const element = document.createElement('div'); + element.name = 'test-name'; + const prefix = 'test-prefix'; + + const output = generateId(element, prefix); + const expectedOutput = `${prefix}-${element.name}-`; + + expect(output).to.contain(expectedOutput); + expect(output).to.have.length(expectedOutput.length + 2); + }); + }); + + describe('when given element has no id value and no name value', () => { + it('generates a unique prefixed id based on 4 random characters', () => { + const element = document.createElement('div'); + const prefix = 'test-prefix'; + + const output = generateId(element, prefix); + const expectedOutput = `${prefix}-`; + + expect(output).to.contain(expectedOutput); + expect(output).to.have.length(expectedOutput.length + 4); + }); + }); + }); + + describe('getType', () => { + it('returns type of given object', () => { + expect(getType({})).to.equal('Object'); + expect(getType(1)).to.equal('Number'); + expect(getType(true)).to.equal('Boolean'); + expect(getType([])).to.equal('Array'); + expect(getType(() => {})).to.equal('Function'); + expect(getType(new Error())).to.equal('Error'); + expect(getType(new RegExp())).to.equal('RegExp'); + expect(getType(new String())).to.equal('String'); // eslint-disable-line + expect(getType('')).to.equal('String'); + }); + }); + + describe('isType', () => { + it('checks with given object type equals given type', () => { + expect(isType('Object', {})).to.equal(true); + expect(isType('String', {})).to.equal(false); + }); + }); + + describe('isElement', () => { + it('checks with given object is an element', () => { + const element = document.createElement('div'); + expect(isElement(element)).to.equal(true); + expect(isElement({})).to.equal(false); + }); + }); + + describe('stripHTML', () => { + it('strips HTML from value', () => { + const value = ''; + const output = stripHTML(value); + expect(output).to.equal( + '<script&rt;somethingMalicious();</script&rt;', + ); + }); + }); + + describe('sortByAlpha', () => { + describe('sorting an array', () => { + it('sorts by value alphabetically', () => { + const values = [ + { value: 'The Strokes' }, + { value: 'Arctic Monkeys' }, + { value: 'Oasis' }, + { value: 'Tame Impala' }, + ]; + + const output = values.sort(sortByAlpha); + + expect(output).to.eql([ + { value: 'Arctic Monkeys' }, + { value: 'Oasis' }, + { value: 'Tame Impala' }, + { value: 'The Strokes' }, + ]); + }); + + it('sorts by label alphabetically', () => { + const values = [ + { label: 'The Strokes' }, + { label: 'Arctic Monkeys' }, + { label: 'Oasis' }, + { label: 'Tame Impala' }, + ]; + + const output = values.sort(sortByAlpha); + + expect(output).to.eql([ + { label: 'Arctic Monkeys' }, + { label: 'Oasis' }, + { label: 'Tame Impala' }, + { label: 'The Strokes' }, + ]); + }); + }); + }); + + describe('sortByScore', () => { + describe('sorting an array', () => { + it('sorts by score ascending', () => { + const values = [ + { score: 10 }, + { score: 3001 }, + { score: 124 }, + { score: 400 }, + ]; + + const output = values.sort(sortByScore); + + expect(output).to.eql([ + { score: 10 }, + { score: 124 }, + { score: 400 }, + { score: 3001 }, + ]); + }); + }); + }); + + describe('dispatchEvent', () => { + it('dispatches custom event of given type on given element', () => { + const fakeElement = { + dispatchEvent: stub(), + }; + const eventType = 'testEvent'; + const customArgs = { + testing: true, + }; + + dispatchEvent(fakeElement, eventType, customArgs); + + expect(fakeElement.dispatchEvent.called).to.equal(true); + const event = fakeElement.dispatchEvent.lastCall.args[0]; + expect(event).to.be.instanceof(CustomEvent); + expect(event.bubbles).to.equal(true); + expect(event.cancelable).to.equal(true); + expect(event.detail).to.equal(customArgs); + }); + }); + + describe('regexFilter', () => { + it('tests given regex against given value', () => { + // An email address regex + const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + + expect(regexFilter('joe@bloggs.com', regex)).to.equal(true); + expect(regexFilter('joe bloggs', regex)).to.equal(false); + }); + }); + + describe('reduceToValues', () => { + it('reduces an array of objects to an array of values using given key', () => { + const values = [ + { name: 'The Strokes' }, + { name: 'Arctic Monkeys' }, + { name: 'Oasis' }, + { name: 'Tame Impala' }, + ]; + + const output = reduceToValues(values, 'name'); + expect(output).to.eql([ + 'The Strokes', + 'Arctic Monkeys', + 'Oasis', + 'Tame Impala', + ]); + }); + }); + + describe('fetchFromObject', () => { + it('fetches value from object using given path', () => { + const object = { + band: { + name: 'The Strokes', + }, + }; + + const output = fetchFromObject(object, 'band.name'); + expect(output).to.equal(object.band.name); + }); + }); + + describe('existsInArray', () => { + it('determines whether a value exists within given array', () => { + const values = [ + { value: 'The Strokes' }, + { value: 'Arctic Monkeys' }, + { value: 'Oasis' }, + { value: 'Tame Impala' }, + ]; + + expect(existsInArray(values, 'Oasis', 'value')).to.equal(true); + expect(existsInArray(values, 'The Beatles', 'value')).to.equal(false); + }); + }); + + describe('cloneObject', () => { + it('deeply clones a given object', () => { + const object = { + levelOne: { + id: 1, + levelTwo: { + id: 2, + levelThree: { + id: 3, + levelFour: { + id: 4, + }, + }, + }, + }, + }; + + const output = cloneObject(object); + + expect(output).to.not.equal(object); + expect(output).to.eql(object); + }); + }); }); diff --git a/src/scripts/reducers/index.test.js b/src/scripts/reducers/index.test.js index 8780069..79c2028 100644 --- a/src/scripts/reducers/index.test.js +++ b/src/scripts/reducers/index.test.js @@ -15,4 +15,43 @@ describe('reducers/rootReducer', () => { expect(state.choices).to.equal(choices(undefined, {})); expect(state.items).to.equal(items(undefined, {})); }); + + describe('CLEAR_ALL', () => { + it('resets state', () => { + const output = rootReducer( + { + items: [1, 2, 3], + groups: [1, 2, 3], + choices: [1, 2, 3], + }, + { + type: 'CLEAR_ALL', + }, + ); + + expect(output).to.eql({ + items: [], + groups: [], + choices: [], + }); + }); + }); + + describe('RESET_TO', () => { + it('replaces state with given state', () => { + const output = rootReducer( + { + items: [1, 2, 3], + groups: [1, 2, 3], + choices: [1, 2, 3], + }, + { + type: 'RESET_TO', + state: {}, + }, + ); + + expect(output).to.eql({}); + }); + }); });