From 971ce3a7f7c84d3cd046b3ad7a35f149594f8b85 Mon Sep 17 00:00:00 2001 From: nlepage <19571875+nlepage@users.noreply.github.com> Date: Wed, 8 Nov 2017 14:43:49 +0100 Subject: [PATCH 01/11] :sparkles: First naive implementation of slice walking --- src/array/convertArrayMethod.js | 6 ++++- src/array/push.spec.js | 24 ++++++++++++++++++ src/core/apply.js | 37 ++++++++++++++++++++++----- src/core/path.utils.js | 10 +++++--- src/core/path.utils.spec.js | 26 ------------------- src/core/set.spec.js | 26 +++++++++++++++++++ src/util/lang.js | 24 ++++++++++++++++++ src/util/lang.spec.js | 44 +++++++++++++++++++++++++++++++++ 8 files changed, 160 insertions(+), 37 deletions(-) diff --git a/src/array/convertArrayMethod.js b/src/array/convertArrayMethod.js index 43883faf..cdcc4e5f 100644 --- a/src/array/convertArrayMethod.js +++ b/src/array/convertArrayMethod.js @@ -1,7 +1,11 @@ import { convert } from 'core/convert' +import { + isNil, +} from 'util/lang' + const copyArray = array => { - if (array === undefined || array === null) return [] + if (isNil(array)) return [] if (Array.isArray(array)) return [...array] return [array] } diff --git a/src/array/push.spec.js b/src/array/push.spec.js index ee898eed..39fb7371 100644 --- a/src/array/push.spec.js +++ b/src/array/push.spec.js @@ -47,4 +47,28 @@ describe('Push', () => { return output }, { nested: { prop: 1 } }, 'nested.prop') }) + + it('should push in several arrays', () => { + immutaTest(input => { + const output = push(input, 'nested.prop[:].arr', 2) + expect(output).toEqual({ + nested: { + prop: [ + { arr: [1, 2] }, + { arr: [1, 2] }, + { arr: [2] }, + ], + }, + }) + return output + }, { + nested: { + prop: [ + { arr: [1] }, + { arr: 1 }, + {}, + ], + }, + }, 'nested.prop') + }) }) diff --git a/src/core/apply.js b/src/core/apply.js index 05711754..95078eea 100644 --- a/src/core/apply.js +++ b/src/core/apply.js @@ -1,8 +1,14 @@ import { - isArrayProp, + isIndex, + isSlice, } from './path.utils' import { unsafeToPath } from './toPath' +import { + isNaturalInteger, + isNil, +} from 'util/lang' + /** * Makes a copy of value. * @function @@ -14,17 +20,25 @@ import { unsafeToPath } from './toPath' * @since 0.4.0 */ const copy = (value, asArray) => { - if (value === undefined || value === null) { - if (asArray) - return [] + if (isNil(value)) { + if (asArray) return [] return {} } if (Array.isArray(value)) return [...value] return { ...value } } +// FIXME move ? +const getLength = value => { + if (isNil(value) || !isNaturalInteger(value.length)) return 0 + return value.length +} + +// FIXME move ? +const min = (a, b) => a < b ? a : b + const callback = (obj, prop) => { - if (obj === undefined || obj === null) return undefined + if (isNil(obj)) return undefined return obj[prop] } @@ -55,9 +69,20 @@ const apply = (obj, path, operation) => { const walkPath = (curObj, curPath) => { const [prop, ...pathRest] = curPath + if (isSlice(prop)) { + const length = getLength(curObj) + const startIndex = prop[0] === undefined ? 0 : min(prop[0], length) + const endIndex = prop[1] === undefined ? length : min(prop[1], length) + + let curSliceObj = curObj + for (let i = startIndex; i < endIndex; i++) + curSliceObj = walkPath(curSliceObj, [i, ...pathRest]) + return curSliceObj + } + const value = callback(curObj, prop) - const newObj = copy(curObj, isArrayProp(prop)) + const newObj = copy(curObj, isIndex(prop)) if (curPath.length === 1) { operation(newObj, prop, value) diff --git a/src/core/path.utils.js b/src/core/path.utils.js index ab577a12..718716c5 100644 --- a/src/core/path.utils.js +++ b/src/core/path.utils.js @@ -1,13 +1,15 @@ +import { + isNaturalInteger, +} from 'util/lang' + /** - * Tests whether arg is a valid index, that is a positive integer. + * This is an alias for {@link util/isNaturalInteger}. * @function - * @param {*} arg The value to test - * @return {boolean} True if arg is a valid index, false otherwise * @memberof core * @private * @since 0.4.0 */ -const isIndex = arg => Number.isSafeInteger(arg) && arg >= 0 +const isIndex = isNaturalInteger /** * Tests whether arg is a valid slice index, that is an integer or undefined. diff --git a/src/core/path.utils.spec.js b/src/core/path.utils.spec.js index 36a49ec4..edc4930c 100644 --- a/src/core/path.utils.spec.js +++ b/src/core/path.utils.spec.js @@ -1,36 +1,10 @@ /* eslint-env jest */ import { - isIndex, isSlice, isSliceIndex, } from './path.utils' describe('Path Utils', () => { - describe('IsIndex', () => { - it('should return true for any non negative integer', () => { - expect(isIndex(0)).toBe(true) - expect(isIndex(1)).toBe(true) - expect(isIndex(6)).toBe(true) - expect(isIndex(100000000000)).toBe(true) - }) - - it('should return false for any negative integer', () => { - expect(isIndex(-1)).toBe(false) - expect(isIndex(-6)).toBe(false) - expect(isIndex(-100000000000)).toBe(false) - }) - - it('should return false for any non integer', () => { - expect(isIndex(undefined)).toBe(false) - expect(isIndex(null)).toBe(false) - expect(isIndex(true)).toBe(false) - expect(isIndex({})).toBe(false) - expect(isIndex([])).toBe(false) - expect(isIndex('')).toBe(false) - expect(isIndex(.6)).toBe(false) - }) - }) - describe('IsSliceIndex', () => { it('should return true for any integer or undefined', () => { expect(isSliceIndex(0)).toBe(true) diff --git a/src/core/set.spec.js b/src/core/set.spec.js index 43bbca60..c1e73c52 100644 --- a/src/core/set.spec.js +++ b/src/core/set.spec.js @@ -31,6 +31,32 @@ describe('Set', () => { }, 'nested.prop.0') }) + it('should set values in an array slice', () => { + immutaTest(input => { + const output = set(input, 'nested.prop[:]', 'final') + expect(output).toEqual({ + nested: { prop: ['final', 'final', 'final'] }, + other: {}, + }) + return output + }, { + nested: { prop: ['a', 'b', 'c'] }, + other: {}, + }, 'nested.prop') + + immutaTest(input => { + const output = set(input, 'nested.prop[1:3].val', 'final') + expect(output).toEqual({ + nested: { prop: [{ val: 'a' }, { val: 'final' }, { val: 'final' }, { val: 'd' }] }, + other: {}, + }) + return output + }, { + nested: { prop: [{ val: 'a' }, { val: 'b' }, { val: 'c' }, { val: 'd' }] }, + other: {}, + }, 'nested.prop') + }) + it('should set a deep undefined prop', () => { immutaTest((input, path) => { const output = set(input, path, 'final') diff --git a/src/util/lang.js b/src/util/lang.js index f24e4dff..d8e646de 100644 --- a/src/util/lang.js +++ b/src/util/lang.js @@ -1,3 +1,25 @@ +/** + * Tests whether arg is a natural integer. + * @function + * @param {*} arg The value to test + * @return {boolean} True if arg is a natural integer, false otherwise + * @memberof util + * @private + * @since 0.4.0 + */ +const isNaturalInteger = arg => Number.isSafeInteger(arg) && arg >= 0 + +/** + * Tests whether arg is a undefined or null. + * @function + * @param {*} arg The value to test + * @return {boolean} True if arg is undefined or null, false otherwise + * @memberof util + * @private + * @since 0.4.0 + */ +const isNil = arg => arg === undefined || arg === null + /** * Tests whether arg is a Symbol. * @param {*} arg The value to test @@ -20,6 +42,8 @@ const isSymbol = arg => typeof arg === 'symbol' const toString = arg => typeof arg === 'string' ? arg : `${arg}` export { + isNaturalInteger, + isNil, isSymbol, toString, } diff --git a/src/util/lang.spec.js b/src/util/lang.spec.js index c39f4fc6..39ce4ff8 100644 --- a/src/util/lang.spec.js +++ b/src/util/lang.spec.js @@ -1,10 +1,54 @@ /* eslint-env jest */ import { + isNaturalInteger, + isNil, isSymbol, toString, } from 'util/lang' describe('Lang utils', () => { + describe('IsNaturalInteger', () => { + it('should return true for any non negative integer', () => { + expect(isNaturalInteger(0)).toBe(true) + expect(isNaturalInteger(1)).toBe(true) + expect(isNaturalInteger(6)).toBe(true) + expect(isNaturalInteger(100000000000)).toBe(true) + }) + + it('should return false for any negative integer', () => { + expect(isNaturalInteger(-1)).toBe(false) + expect(isNaturalInteger(-6)).toBe(false) + expect(isNaturalInteger(-100000000000)).toBe(false) + }) + + it('should return false for any non integer', () => { + expect(isNaturalInteger(undefined)).toBe(false) + expect(isNaturalInteger(null)).toBe(false) + expect(isNaturalInteger(true)).toBe(false) + expect(isNaturalInteger({})).toBe(false) + expect(isNaturalInteger([])).toBe(false) + expect(isNaturalInteger('')).toBe(false) + expect(isNaturalInteger(.6)).toBe(false) + }) + }) + + describe('IsNil', () => { + it('should return true for undefined and null', () => { + expect(isNil(undefined)).toBe(true) + expect(isNil(null)).toBe(true) + }) + + it('should return false for any other value than undefined and null', () => { + expect(isNil(true)).toBe(false) + expect(isNil({})).toBe(false) + expect(isNil([])).toBe(false) + expect(isNil('')).toBe(false) + expect(isNil(.6)).toBe(false) + expect(isNil('null')).toBe(false) + expect(isNil('undefined')).toBe(false) + }) + }) + describe('IsSymbol', () => { it('should return true for symbols', () => { expect(isSymbol(Symbol())).toBe(true) From a7aaf27d8fb502fd6845890f193c8c4ef5231116 Mon Sep 17 00:00:00 2001 From: nlepage <19571875+nlepage@users.noreply.github.com> Date: Fri, 10 Nov 2017 12:28:09 +0100 Subject: [PATCH 02/11] :sparkles: Support negative slice indexes --- src/core/apply.js | 20 +++++++++++++------- src/core/set.spec.js | 12 ++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/core/apply.js b/src/core/apply.js index 95078eea..93c1bf0d 100644 --- a/src/core/apply.js +++ b/src/core/apply.js @@ -34,14 +34,22 @@ const getLength = value => { return value.length } -// FIXME move ? -const min = (a, b) => a < b ? a : b - const callback = (obj, prop) => { if (isNil(obj)) return undefined return obj[prop] } +const getSliceBound = (value, defaultValue, length) => { + if (value === undefined) return defaultValue + if (value < 0) return Math.max(length + value, 0) + return Math.min(value, length) +} + +const getSliceBounds = ([start, end], length) => ([ + getSliceBound(start, 0, length), + getSliceBound(end, length, length), +]) + /** * Operation to apply on a nested property of an object, to be called by {@link core.apply|apply}. * @memberof core @@ -70,12 +78,10 @@ const apply = (obj, path, operation) => { const [prop, ...pathRest] = curPath if (isSlice(prop)) { - const length = getLength(curObj) - const startIndex = prop[0] === undefined ? 0 : min(prop[0], length) - const endIndex = prop[1] === undefined ? length : min(prop[1], length) + const [start, end] = getSliceBounds(prop, getLength(curObj)) let curSliceObj = curObj - for (let i = startIndex; i < endIndex; i++) + for (let i = start; i < end; i++) curSliceObj = walkPath(curSliceObj, [i, ...pathRest]) return curSliceObj } diff --git a/src/core/set.spec.js b/src/core/set.spec.js index c1e73c52..d84945c5 100644 --- a/src/core/set.spec.js +++ b/src/core/set.spec.js @@ -55,6 +55,18 @@ describe('Set', () => { nested: { prop: [{ val: 'a' }, { val: 'b' }, { val: 'c' }, { val: 'd' }] }, other: {}, }, 'nested.prop') + + immutaTest(input => { + const output = set(input, 'nested.prop[-3:-1].val', 'final') + expect(output).toEqual({ + nested: { prop: [{ val: 'a' }, { val: 'final' }, { val: 'final' }, { val: 'd' }] }, + other: {}, + }) + return output + }, { + nested: { prop: [{ val: 'a' }, { val: 'b' }, { val: 'c' }, { val: 'd' }] }, + other: {}, + }, 'nested.prop') }) it('should set a deep undefined prop', () => { From a821b1ede1d0633384f4942a0bb57cd900a51836 Mon Sep 17 00:00:00 2001 From: nlepage <19571875+nlepage@users.noreply.github.com> Date: Sun, 12 Nov 2017 19:07:25 +0100 Subject: [PATCH 03/11] :zap: Avoid multiple array reinstanciations --- src/core/apply.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/apply.js b/src/core/apply.js index 93c1bf0d..21658c31 100644 --- a/src/core/apply.js +++ b/src/core/apply.js @@ -74,21 +74,22 @@ const getSliceBounds = ([start, end], length) => ([ * @since 0.4.0 */ const apply = (obj, path, operation) => { - const walkPath = (curObj, curPath) => { + const walkPath = (curObj, curPath, doCopy = true) => { const [prop, ...pathRest] = curPath if (isSlice(prop)) { const [start, end] = getSliceBounds(prop, getLength(curObj)) - let curSliceObj = curObj + const newArr = copy(curObj, true) for (let i = start; i < end; i++) - curSliceObj = walkPath(curSliceObj, [i, ...pathRest]) - return curSliceObj + walkPath(newArr, [i, ...pathRest], false) + return newArr } const value = callback(curObj, prop) - const newObj = copy(curObj, isIndex(prop)) + let newObj = curObj + if (doCopy) newObj = copy(curObj, isIndex(prop)) if (curPath.length === 1) { operation(newObj, prop, value) From 124e1ef3dfb2dc5fe9cd4ba9536e59e949882cb7 Mon Sep 17 00:00:00 2001 From: nlepage <19571875+nlepage@users.noreply.github.com> Date: Sun, 12 Nov 2017 21:45:20 +0100 Subject: [PATCH 04/11] :bulb: fix operation callback jsdoc --- src/core/apply.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/apply.js b/src/core/apply.js index 21658c31..ae17405f 100644 --- a/src/core/apply.js +++ b/src/core/apply.js @@ -55,7 +55,7 @@ const getSliceBounds = ([start, end], length) => ([ * @memberof core * @callback operation * @param {*} obj The last nested object - * @param {string|number|Array} prop The prop of the last nested object + * @param {string|number} prop The prop of the last nested object * @param {*} value The value of the prop * @returns {*} Result of the operation * @private From 8ebda31bb5f0a4e6d15449553836b909d4c7c910 Mon Sep 17 00:00:00 2001 From: nlepage <19571875+nlepage@users.noreply.github.com> Date: Sun, 12 Nov 2017 21:47:02 +0100 Subject: [PATCH 05/11] :hammer: Do not cap slice bounds at length --- src/core/apply.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/apply.js b/src/core/apply.js index ae17405f..95e3801a 100644 --- a/src/core/apply.js +++ b/src/core/apply.js @@ -42,7 +42,7 @@ const callback = (obj, prop) => { const getSliceBound = (value, defaultValue, length) => { if (value === undefined) return defaultValue if (value < 0) return Math.max(length + value, 0) - return Math.min(value, length) + return value } const getSliceBounds = ([start, end], length) => ([ From a481a959b29eeb886b22649d56a0e9d9424b029f Mon Sep 17 00:00:00 2001 From: nlepage <19571875+nlepage@users.noreply.github.com> Date: Sun, 12 Nov 2017 22:49:56 +0100 Subject: [PATCH 06/11] :truck::fire: Move around some utils code, remove unused isArrayProp, inline apply callback for now --- src/core/apply.js | 31 ++++++------------------------- src/core/path.utils.js | 32 ++++++++++++++++++++------------ src/core/path.utils.spec.js | 13 +++++++++++++ src/util/lang.js | 13 +++++++++++++ src/util/lang.spec.js | 16 ++++++++++++++++ 5 files changed, 68 insertions(+), 37 deletions(-) diff --git a/src/core/apply.js b/src/core/apply.js index 95e3801a..98755df5 100644 --- a/src/core/apply.js +++ b/src/core/apply.js @@ -1,12 +1,13 @@ import { + getSliceBounds, isIndex, isSlice, } from './path.utils' import { unsafeToPath } from './toPath' import { - isNaturalInteger, isNil, + length, } from 'util/lang' /** @@ -28,28 +29,6 @@ const copy = (value, asArray) => { return { ...value } } -// FIXME move ? -const getLength = value => { - if (isNil(value) || !isNaturalInteger(value.length)) return 0 - return value.length -} - -const callback = (obj, prop) => { - if (isNil(obj)) return undefined - return obj[prop] -} - -const getSliceBound = (value, defaultValue, length) => { - if (value === undefined) return defaultValue - if (value < 0) return Math.max(length + value, 0) - return value -} - -const getSliceBounds = ([start, end], length) => ([ - getSliceBound(start, 0, length), - getSliceBound(end, length, length), -]) - /** * Operation to apply on a nested property of an object, to be called by {@link core.apply|apply}. * @memberof core @@ -78,15 +57,17 @@ const apply = (obj, path, operation) => { const [prop, ...pathRest] = curPath if (isSlice(prop)) { - const [start, end] = getSliceBounds(prop, getLength(curObj)) + const [start, end] = getSliceBounds(prop, length(curObj)) const newArr = copy(curObj, true) + for (let i = start; i < end; i++) walkPath(newArr, [i, ...pathRest], false) + return newArr } - const value = callback(curObj, prop) + const value = isNil(curObj) ? undefined : curObj[prop] let newObj = curObj if (doCopy) newObj = copy(curObj, isIndex(prop)) diff --git a/src/core/path.utils.js b/src/core/path.utils.js index 718716c5..041138cb 100644 --- a/src/core/path.utils.js +++ b/src/core/path.utils.js @@ -2,6 +2,25 @@ import { isNaturalInteger, } from 'util/lang' +const getSliceBound = (value, defaultValue, length) => { + if (value === undefined) return defaultValue + if (value < 0) return Math.max(length + value, 0) + return value +} + +/** + * Get the actual bounds of a slice. + * @param {Array} bounds The bounds of the slice + * @param {number} length The length of the actual array + * @returns {Array} The actual bounds of the slice + * @private + * @since 0.4.0 + */ +const getSliceBounds = ([start, end], length) => ([ + getSliceBound(start, 0, length), + getSliceBound(end, length, length), +]) + /** * This is an alias for {@link util/isNaturalInteger}. * @function @@ -37,19 +56,8 @@ const isSlice = arg => { return isSliceIndex(arg[0]) && isSliceIndex(arg[1]) } -/** - * Tests whether arg is either an index or a slice. - * @function - * @param {*} arg The value to test - * @return {boolean} True if arg is either an index or a slice, false otherwise - * @memberof core - * @private - * @since 0.4.0 - */ -const isArrayProp = arg => isIndex(arg) || isSlice(arg) - export { - isArrayProp, + getSliceBounds, isIndex, isSlice, isSliceIndex, diff --git a/src/core/path.utils.spec.js b/src/core/path.utils.spec.js index edc4930c..3e9bafd4 100644 --- a/src/core/path.utils.spec.js +++ b/src/core/path.utils.spec.js @@ -1,10 +1,23 @@ /* eslint-env jest */ import { + getSliceBounds, isSlice, isSliceIndex, } from './path.utils' describe('Path Utils', () => { + describe('GetSliceBounds', () => { + it('should return actual slice bounds', () => { + expect(getSliceBounds([undefined, undefined], 0)).toEqual([0, 0]) + expect(getSliceBounds([-2, -1], 0)).toEqual([0, 0]) + expect(getSliceBounds([1, 2], 0)).toEqual([1, 2]) + + expect(getSliceBounds([undefined, undefined], 6)).toEqual([0, 6]) + expect(getSliceBounds([1, -1], 6)).toEqual([1, 5]) + expect(getSliceBounds([7, 8], 6)).toEqual([7, 8]) + }) + }) + describe('IsSliceIndex', () => { it('should return true for any integer or undefined', () => { expect(isSliceIndex(0)).toBe(true) diff --git a/src/util/lang.js b/src/util/lang.js index d8e646de..7babf74c 100644 --- a/src/util/lang.js +++ b/src/util/lang.js @@ -31,6 +31,18 @@ const isNil = arg => arg === undefined || arg === null */ const isSymbol = arg => typeof arg === 'symbol' +/** + * Returns the length of arg. + * @param {*} arg The value of which length must be returned + * @returns {number} The length of arg + * @private + * @since 0.4.0 + */ +const length = arg => { + if (isNil(arg) || !isNaturalInteger(arg.length)) return 0 + return arg.length +} + /** * Converts arg to a string using string interpolation. * @param {*} arg The value to convert @@ -45,5 +57,6 @@ export { isNaturalInteger, isNil, isSymbol, + length, toString, } diff --git a/src/util/lang.spec.js b/src/util/lang.spec.js index 39ce4ff8..ceb8ae9b 100644 --- a/src/util/lang.spec.js +++ b/src/util/lang.spec.js @@ -3,6 +3,7 @@ import { isNaturalInteger, isNil, isSymbol, + length, toString, } from 'util/lang' @@ -63,6 +64,21 @@ describe('Lang utils', () => { }) }) + describe('Length', () => { + it('should return length of array', () => { + expect(length(Array(666))).toBe(666) + expect(length([])).toBe(0) + expect(length([1, 2, 3])).toBe(3) + }) + + it('should return zero if arg has no length', () => { + expect(length({})).toBe(0) + expect(length(666)).toBe(0) + expect(length()).toBe(0) + expect(length(null)).toBe(0) + }) + }) + describe('ToString', () => { it('should return string representation', () => { expect(toString()).toBe('undefined') From 0313da6cf0eca4c2b9b28e398f3c733ab0f0c699 Mon Sep 17 00:00:00 2001 From: nlepage <19571875+nlepage@users.noreply.github.com> Date: Sun, 12 Nov 2017 23:03:23 +0100 Subject: [PATCH 07/11] :rotating_light: fix lint after rebase --- src/core/apply.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/apply.js b/src/core/apply.js index 98755df5..579c0f98 100644 --- a/src/core/apply.js +++ b/src/core/apply.js @@ -3,13 +3,14 @@ import { isIndex, isSlice, } from './path.utils' -import { unsafeToPath } from './toPath' import { isNil, length, } from 'util/lang' +import { unsafeToPath } from './toPath' + /** * Makes a copy of value. * @function From f1116e9cdac1a44de47b00a0c46e94a05e046d9d Mon Sep 17 00:00:00 2001 From: nlepage <19571875+nlepage@users.noreply.github.com> Date: Sun, 12 Nov 2017 23:13:22 +0100 Subject: [PATCH 08/11] :truck: Make apply's own test file --- src/array/push.spec.js | 24 ------------------------ src/core/apply.spec.js | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 src/core/apply.spec.js diff --git a/src/array/push.spec.js b/src/array/push.spec.js index 39fb7371..ee898eed 100644 --- a/src/array/push.spec.js +++ b/src/array/push.spec.js @@ -47,28 +47,4 @@ describe('Push', () => { return output }, { nested: { prop: 1 } }, 'nested.prop') }) - - it('should push in several arrays', () => { - immutaTest(input => { - const output = push(input, 'nested.prop[:].arr', 2) - expect(output).toEqual({ - nested: { - prop: [ - { arr: [1, 2] }, - { arr: [1, 2] }, - { arr: [2] }, - ], - }, - }) - return output - }, { - nested: { - prop: [ - { arr: [1] }, - { arr: 1 }, - {}, - ], - }, - }, 'nested.prop') - }) }) diff --git a/src/core/apply.spec.js b/src/core/apply.spec.js new file mode 100644 index 00000000..5f05f4c9 --- /dev/null +++ b/src/core/apply.spec.js @@ -0,0 +1,40 @@ +/* eslint-env jest */ +import { apply } from './apply' +import { immutaTest } from 'test.utils' + +describe('Apply', () => { + + const _inc = (v, i = 1) => { + let r = Number(v) + if (Number.isNaN(r)) r = 0 + return r + i + } + + const inc = (obj, path, ...args) => apply(obj, path, (obj, prop) => { obj[prop] = _inc(obj[prop], ...args) }) + + it('should inc in all an array', () => { + immutaTest(input => { + const output = inc(input, 'nested.prop[:].val', 2) + expect(output).toEqual({ + nested: { + prop: [ + { val: 6 }, + { val: -6 }, + { val: 2 }, + { val: 2 }, + ], + }, + }) + return output + }, { + nested: { + prop: [ + { val: 4 }, + { val: -8 }, + { val: 'a' }, + {}, + ], + }, + }, 'nested.prop') + }) +}) From 23edb077c69eb1ecf55094b48b89e0fbd365cad4 Mon Sep 17 00:00:00 2001 From: nlepage <19571875+nlepage@users.noreply.github.com> Date: Sun, 12 Nov 2017 23:19:16 +0100 Subject: [PATCH 09/11] :hammer: Finer mutations detection in tests --- src/core/apply.spec.js | 49 ++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/core/apply.spec.js b/src/core/apply.spec.js index 5f05f4c9..dd43edc4 100644 --- a/src/core/apply.spec.js +++ b/src/core/apply.spec.js @@ -13,28 +13,41 @@ describe('Apply', () => { const inc = (obj, path, ...args) => apply(obj, path, (obj, prop) => { obj[prop] = _inc(obj[prop], ...args) }) it('should inc in all an array', () => { - immutaTest(input => { - const output = inc(input, 'nested.prop[:].val', 2) - expect(output).toEqual({ + immutaTest( + input => { + const output = inc(input, 'nested.prop[:].val', 2) + expect(output).toEqual({ + nested: { + prop: [ + { + val: 6, + other: {}, + }, + { val: -6 }, + { val: 2 }, + { val: 2 }, + ], + }, + }) + return output + }, + { nested: { prop: [ - { val: 6 }, - { val: -6 }, - { val: 2 }, - { val: 2 }, + { + val: 4, + other: {}, + }, + { val: -8 }, + { val: 'a' }, + {}, ], }, - }) - return output - }, { - nested: { - prop: [ - { val: 4 }, - { val: -8 }, - { val: 'a' }, - {}, - ], }, - }, 'nested.prop') + 'nested.prop.0.val', + 'nested.prop.1.val', + 'nested.prop.2.val', + 'nested.prop.3.val', + ) }) }) From 5aae42730bac6a38603d54608dd19b0353a55ca7 Mon Sep 17 00:00:00 2001 From: nlepage <19571875+nlepage@users.noreply.github.com> Date: Sun, 12 Nov 2017 23:26:01 +0100 Subject: [PATCH 10/11] :truck: Move tests in apply's own test file --- src/core/apply.spec.js | 57 +++++++++++++++++++++++++++++++++++++++++- src/core/set.spec.js | 38 ---------------------------- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/src/core/apply.spec.js b/src/core/apply.spec.js index dd43edc4..0fffc567 100644 --- a/src/core/apply.spec.js +++ b/src/core/apply.spec.js @@ -12,7 +12,7 @@ describe('Apply', () => { const inc = (obj, path, ...args) => apply(obj, path, (obj, prop) => { obj[prop] = _inc(obj[prop], ...args) }) - it('should inc in all an array', () => { + it('should inc in an array slice', () => { immutaTest( input => { const output = inc(input, 'nested.prop[:].val', 2) @@ -49,5 +49,60 @@ describe('Apply', () => { 'nested.prop.2.val', 'nested.prop.3.val', ) + + immutaTest( + input => { + const output = inc(input, 'nested.prop[1:3].val') + expect(output).toEqual({ + nested: { + prop: [{ val: 0 }, { + val: 2, + other: {}, + }, { val: 3 }, { val: 3 }], + }, + other: {}, + }) + return output + }, + { + nested: { + prop: [{ val: 0 }, { + val: 1, + other: {}, + }, { val: 2 }, { val: 3 }], + }, + other: {}, + }, + 'nested.prop.1.val', + 'nested.prop.2.val', + ) + + immutaTest( + input => { + const output = inc(input, 'nested.prop[-3:-1].val') + expect(output).toEqual({ + nested: { + prop: [{ val: 0 }, { + val: 2, + other: {}, + }, { val: 3 }, { val: 3 }], + }, + other: {}, + }) + return output + }, + { + nested: { + prop: [{ val: 0 }, { + val: 1, + other: {}, + }, { val: 2 }, { val: 3 }], + }, + other: {}, + }, + 'nested.prop.1.val', + 'nested.prop.2.val', + ) }) + }) diff --git a/src/core/set.spec.js b/src/core/set.spec.js index d84945c5..43bbca60 100644 --- a/src/core/set.spec.js +++ b/src/core/set.spec.js @@ -31,44 +31,6 @@ describe('Set', () => { }, 'nested.prop.0') }) - it('should set values in an array slice', () => { - immutaTest(input => { - const output = set(input, 'nested.prop[:]', 'final') - expect(output).toEqual({ - nested: { prop: ['final', 'final', 'final'] }, - other: {}, - }) - return output - }, { - nested: { prop: ['a', 'b', 'c'] }, - other: {}, - }, 'nested.prop') - - immutaTest(input => { - const output = set(input, 'nested.prop[1:3].val', 'final') - expect(output).toEqual({ - nested: { prop: [{ val: 'a' }, { val: 'final' }, { val: 'final' }, { val: 'd' }] }, - other: {}, - }) - return output - }, { - nested: { prop: [{ val: 'a' }, { val: 'b' }, { val: 'c' }, { val: 'd' }] }, - other: {}, - }, 'nested.prop') - - immutaTest(input => { - const output = set(input, 'nested.prop[-3:-1].val', 'final') - expect(output).toEqual({ - nested: { prop: [{ val: 'a' }, { val: 'final' }, { val: 'final' }, { val: 'd' }] }, - other: {}, - }) - return output - }, { - nested: { prop: [{ val: 'a' }, { val: 'b' }, { val: 'c' }, { val: 'd' }] }, - other: {}, - }, 'nested.prop') - }) - it('should set a deep undefined prop', () => { immutaTest((input, path) => { const output = set(input, path, 'final') From ddf4c503a09d913eb04ba7c584e4529b0fa4eb73 Mon Sep 17 00:00:00 2001 From: nlepage <19571875+nlepage@users.noreply.github.com> Date: Sun, 12 Nov 2017 23:57:30 +0100 Subject: [PATCH 11/11] :white_check_mark: Test out of bound slice --- src/core/apply.spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/core/apply.spec.js b/src/core/apply.spec.js index 0fffc567..5df139cc 100644 --- a/src/core/apply.spec.js +++ b/src/core/apply.spec.js @@ -105,4 +105,21 @@ describe('Apply', () => { ) }) + immutaTest( + input => { + const output = inc(input, 'nested.prop[3:5].val', 6) + expect(output).toEqual({ + nested: { prop: [{ val: 0 }, { val: 1 }, undefined, { val: 6 }, { val: 6 }] }, + other: {}, + }) + return output + }, + { + nested: { prop: [{ val: 0 }, { val: 1 }] }, + other: {}, + }, + 'nested.prop.2', + 'nested.prop.3.val', + 'nested.prop.4.val', + ) })