diff --git a/object.js b/object.js index 54ab830..6f0d8c9 100644 --- a/object.js +++ b/object.js @@ -1,12 +1,17 @@ -export const create = Object.create(null) +/** + * @return {Object} obj + */ +export const create = () => Object.create(null) +/** + * @param {Object} obj + */ export const keys = Object.keys /** - * @template V - * @param {Object} obj - * @param {function(V,string):any} f + * @param {Object} obj + * @param {function(any,string):any} f */ export const forEach = (obj, f) => { for (let key in obj) { @@ -14,6 +19,12 @@ export const forEach = (obj, f) => { } } +/** + * @template R + * @param {Object} obj + * @param {function(any,string):R} f + * @return {Array} + */ export const map = (obj, f) => { const results = [] for (let key in obj) { @@ -22,8 +33,31 @@ export const map = (obj, f) => { return results } -export const length = obj => Object.keys(obj).length +/** + * @param {Object} obj + * @return {number} + */ +export const length = obj => keys(obj).length + +/** + * @param {Object} obj + * @param {function(any,string):boolean} f + * @return {boolean} + */ +export const some = (obj, f) => { + for (let key in obj) { + if (f(obj[key], key)) { + return true + } + } + return false +} +/** + * @param {Object} obj + * @param {function(any,string):boolean} f + * @return {boolean} + */ export const every = (obj, f) => { for (let key in obj) { if (!f(obj[key], key)) { @@ -33,6 +67,9 @@ export const every = (obj, f) => { return true } -export const some = (obj, f) => every(obj, () => !f()) - -export const equalFlat = (a, b) => a === b || (length(a) === length(b) && every(a, (val, key) => b[key] === val)) +/** + * @param {Object} a + * @param {Object} b + * @return {boolean} + */ +export const equalFlat = (a, b) => a === b || (length(a) === length(b) && every(a, (val, key) => (val !== undefined || b.hasOwnProperty(key)) && b[key] === val)) diff --git a/object.test.js b/object.test.js new file mode 100644 index 0000000..d4fea83 --- /dev/null +++ b/object.test.js @@ -0,0 +1,31 @@ +import * as t from './testing.js' +import * as object from './object.js' +import * as math from './math.js' + +export const testObject = tc => { + t.assert(object.create().constructor === undefined, 'object.create creates an empty object without constructor') + t.describe('object.equalFlat') + t.assert(object.equalFlat({}, {}), 'comparing equal objects') + t.assert(object.equalFlat({ 'x': 1 }, { 'x': 1 }), 'comparing equal objects') + t.assert(object.equalFlat({ x: 'dtrn' }, { x: 'dtrn' }), 'comparing equal objects') + t.assert(!object.equalFlat({ x: {} }, { x: {} }), 'flatEqual does not dive deep') + t.assert(object.equalFlat({ x: undefined }, { x: undefined }), 'flatEqual handles undefined') + t.assert(!object.equalFlat({ x: undefined }, { y: {} }), 'flatEqual handles undefined') + t.describe('object.every') + t.assert(object.every({ a: 1, b: 3 }, (v, k) => (v % 2) === 1 && k !== 'c')) + t.assert(!object.every({ a: 1, b: 3, c: 5 }, (v, k) => (v % 2) === 1 && k !== 'c')) + t.describe('object.some') + t.assert(object.some({ a: 1, b: 3 }, (v, k) => v === 3 && k === 'b')) + t.assert(!object.some({ a: 1, b: 5 }, (v, k) => v === 3)) + t.assert(object.some({ a: 1, b: 5 }, () => true)) + t.assert(!object.some({ a: 1, b: 5 }, (v, k) => false)) + t.describe('object.forEach') + let forEachSum = 0 + object.forEach({ x: 1, y: 3 }, (v, k) => { forEachSum += v }) + t.assert(forEachSum === 4) + t.describe('object.map') + t.assert(object.map({ x: 1, z: 5 }, (v, k) => v).reduce(math.add) === 6) + t.describe('object.length') + t.assert(object.length({}) === 0) + t.assert(object.length({ x: 1 }) === 1) +} diff --git a/test.js b/test.js index 80ad944..8d32fd2 100644 --- a/test.js +++ b/test.js @@ -16,6 +16,7 @@ import * as map from './map.test.js' import * as eventloop from './eventloop.test.js' import * as time from './time.test.js' import * as pair from './pair.test.js' +import * as object from './object.test.js' import { isBrowser, isNode } from './environment.js' @@ -39,7 +40,8 @@ runTests({ map, eventloop, time, - pair + pair, + object }).then(success => { /* istanbul ignore next */ if (isNode) { diff --git a/testing.js b/testing.js index bb524e1..2ae968c 100644 --- a/testing.js +++ b/testing.js @@ -235,7 +235,12 @@ const _compare = (a, b, path, message, customCompare) => { if (object.length(a) !== object.length(b)) { _failMessage(message, 'Objects have a different number of attributes', path) } - object.forEach(a, (value, key) => _compare(value, b[key], `${path}["${key}"]`, message, customCompare)) + object.forEach(a, (value, key) => { + if (!b.hasOwnProperty(key)) { + _failMessage(message, `Property ${path} does not exist on second argument`, path) + } + b.hasOwnProperty(key) && _compare(value, b[key], `${path}["${key}"]`, message, customCompare) + }) break case Array: if (a.length !== b.length) { @@ -260,14 +265,18 @@ export const assert = (condition, message = null) => condition ? (message !== null && log.print(log.GREEN, log.BOLD, '√ ', log.UNBOLD, message)) : fail(message ? `Failed condition: ${message}` : 'Assertion failed') -export const fails = (f, message) => { +export const fails = f => { let err = null try { f() } catch (_err) { err = _err + log.print(log.GREEN, '⇖ This Error was expected') + } + /* istanbul ignore if */ + if (err === null) { + fail('Expected this to fail') } - assert(err !== null, message) } /** diff --git a/testing.test.js b/testing.test.js index 9fc7dd9..20a2c91 100644 --- a/testing.test.js +++ b/testing.test.js @@ -8,9 +8,16 @@ export const testComparing = () => { t.compare([1, 2], [1, 2], 'simple compare (array)') t.compare({ a: [1, 2] }, { a: [1, 2] }, 'simple compare nested') + t.describe('The following errors are expected!') t.fails(() => { t.compare({ a: 4 }, { b: 5 }, 'childs are not equal') }) + t.fails(() => { + t.compare({ a: 4 }, { a: 5 }, 'childs are not equal') + }) + t.fails(() => { + t.compare({ a: 4 }, null, 'childs are not equal') + }) t.fails(() => { t.compare({ a: 4 }, [4], 'childs have different types') }) @@ -40,6 +47,12 @@ export const testComparing = () => { t.fails(() => { t.compareObjects({ x: 3 }, { x: 1 }) // Compare different objects -- no message }) + t.fails(() => { + t.compare({ x: undefined }, { y: 1 }, 'compare correctly handles undefined') + }) + t.fails(() => { + t.compareObjects({ x: undefined }, { y: 1 }, 'compare correctly handles undefined') + }) } export const testFailing = () => {