Skip to content

Commit

Permalink
✨ Add support of negative indexes in array notation (#291)
Browse files Browse the repository at this point in the history
Add support of negative indexes in array notation
fix #169
  • Loading branch information
frinyvonnick authored and nlepage committed Jan 22, 2019
1 parent e9bcfda commit db4f78b
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 14 deletions.
6 changes: 5 additions & 1 deletion packages/immutadot-parser/src/toPath.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,15 @@ describe('path.toPath', () => {
])
})
it('should convert array notation path', () => {
expect(toPath('[0]["1.2"][\'[1.2]\']["[\\"1.2\\"]"][1a][1[2]')).toEqual([
expect(toPath('[0][-2]["1.2"][\'[1.2]\']["[\\"1.2\\"]"][1a][1[2]')).toEqual([
[
index,
0,
],
[
index,
-2,
],
[
prop,
'1.2',
Expand Down
13 changes: 1 addition & 12 deletions packages/immutadot-parser/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,14 @@ const isNil = arg => arg === undefined || arg === null
*/
const toString = arg => typeof arg === 'string' ? arg : `${arg}`

/**
* Tests whether <code>arg</code> is a natural integer.
* @function
* @param {*} arg The value to test
* @return {boolean} True if <code>arg</code> is a natural integer, false otherwise
* @memberof util
* @private
* @since 1.0.0
*/
const isNaturalInteger = arg => Number.isSafeInteger(arg) && arg >= 0

/**
* This is an alias for {@link util/isNaturalInteger}.
* @function
* @memberof path
* @private
* @since 1.0.0
*/
const isIndex = isNaturalInteger
const isIndex = Number.isSafeInteger

/**
* Tests whether <code>arg</code> is a valid slice index, that is an integer or <code>undefined</code>.
Expand Down
18 changes: 18 additions & 0 deletions packages/immutadot/src/core/flow.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
update,
} from 'core'
import { immutaTest } from 'test.utils'
import { push } from 'array'
describe('flow.flow', () => {
const object = {
nested1: {
Expand Down Expand Up @@ -115,4 +116,21 @@ describe('flow.flow', () => {
other: {},
})
})
it('should update two different array indexes', () => {
immutaTest({
nested: { prop: [{ val: 1 }, { val: 2 }] },
other: {},
}, ['nested.prop.0.val', 'nested.prop.1.val', 'nested.prop.2'], input => {
const output = flow(
set('nested.prop[-2].val', 666),
push('nested.prop', { val: 3 }),
set('nested.prop[-2].val', 666),
)(input)
expect(output).toEqual({
nested: { prop: [{ val: 666 }, { val: 666 }, { val: 3 }] },
other: {},
})
return output
})
})
})
7 changes: 7 additions & 0 deletions packages/immutadot/src/path/apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@immutadot/parser'

import {
getArrayIndex,
getSliceBounds,
pathAlreadyApplied,
} from './utils'
Expand Down Expand Up @@ -128,6 +129,12 @@ const apply = operation => {
return [false, newObj]
}

if (propType === index && propValue < 0) {
const actualIndex = getArrayIndex(propValue, length(curObj))
if (actualIndex === undefined) return [true, curObj]
return walkPath(curObj, curPath, [[index, actualIndex], ...pathRest])
}

const value = isNil(curObj) ? undefined : curObj[propValue]
const doCopy = !isCopy && !pathAlreadyApplied(curPath, appliedPaths)

Expand Down
18 changes: 18 additions & 0 deletions packages/immutadot/src/path/apply.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ describe('path.apply', () => {
obj[prop] = _inc(obj[prop], ...args)
}
const inc = apply(incOperation)
it('should inc element at negative position in array', () => {
immutaTest({ nested: { prop: [0, 1, 2, 3] } },
['nested.prop.3'],
input => {
const output = inc(input, 'nested.prop[-1]', 1)
expect(output).toEqual({ nested: { prop: [0, 1, 2, 4] } })
return output
})
})
it('should do nothing for out of bounds negative array index', () => {
immutaTest({ nested: { prop: [0, 1, 2, 3] } },
[],
input => {
const output = inc(input, 'nested.prop[-5]', 1)
expect(output).toEqual({ nested: { prop: [0, 1, 2, 3] } })
return output
})
})
it('should inc in an array slice', () => {
immutaTest({
nested: {
Expand Down
18 changes: 17 additions & 1 deletion packages/immutadot/src/path/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,23 @@ import {
slice,
} from '@immutadot/parser'

export const getSliceBound = (value, length) => {
/**
* Get the actual array index for negative array indexes.<br/>
* If the index is out of bounds <code>undefined</code> is returned.
* @function
* @memberof path
* @param {number} value The negative index
* @param {number} length The length of the actual array
* @returns {number?} The actual array index or undefined
* @private
* @since 1.0.0
*/
export function getArrayIndex(value, length) {
if (-value > length) return undefined
return Math.max(length + value, 0)
}

const getSliceBound = (value, length) => {
if (value < 0) return Math.max(length + value, 0)
return value
}
Expand Down

0 comments on commit db4f78b

Please sign in to comment.