immutable-ops
Advanced tools
Comparing version 0.4.2 to 0.5.0
448
lib/index.js
@@ -1,38 +0,21 @@ | ||
'use strict'; | ||
import curry from 'ramda/src/curry'; | ||
import placeholder from 'ramda/src/__'; | ||
Object.defineProperty(exports, '__esModule', { | ||
value: true | ||
}); | ||
exports.canMutate = canMutate; | ||
exports['default'] = getImmutableOps; | ||
function forOwn(obj, fn) { | ||
for (const key in obj) { | ||
if (obj.hasOwnProperty(key)) { | ||
fn(obj[key], key); | ||
} | ||
} | ||
} | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
function isArrayLike(value) { | ||
return value && typeof value === 'object' && typeof value.length === 'number' && value.length >= 0 && value.length % 1 === 0; | ||
} | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } } | ||
const OWNER_ID_TAG = '@@_______immutableOpsOwnerID'; | ||
var _lodashForOwn = require('lodash/forOwn'); | ||
var _lodashForOwn2 = _interopRequireDefault(_lodashForOwn); | ||
var _lodashIsArrayLike = require('lodash/isArrayLike'); | ||
var _lodashIsArrayLike2 = _interopRequireDefault(_lodashIsArrayLike); | ||
var _ramdaSrcCurry = require('ramda/src/curry'); | ||
var _ramdaSrcCurry2 = _interopRequireDefault(_ramdaSrcCurry); | ||
var _ramdaSrcWrap = require('ramda/src/wrap'); | ||
var _ramdaSrcWrap2 = _interopRequireDefault(_ramdaSrcWrap); | ||
var _ramdaSrc__ = require('ramda/src/__'); | ||
var _ramdaSrc__2 = _interopRequireDefault(_ramdaSrc__); | ||
var MUTABILITY_TAG = '@@_______canMutate'; | ||
function fastArrayCopy(arr) { | ||
var copied = new Array(arr.length); | ||
for (var i = 0; i < arr.length; i++) { | ||
const copied = new Array(arr.length); | ||
for (let i = 0; i < arr.length; i++) { | ||
copied[i] = arr[i]; | ||
@@ -43,9 +26,14 @@ } | ||
function canMutate(obj) { | ||
return obj.hasOwnProperty(MUTABILITY_TAG); | ||
export function canMutate(obj, ownerID) { | ||
if (!ownerID) return false; | ||
return obj[OWNER_ID_TAG] === ownerID; | ||
} | ||
function addCanMutateTag(opts, obj) { | ||
Object.defineProperty(obj, MUTABILITY_TAG, { | ||
value: true, | ||
const newOwnerID = typeof Symbol === 'function' ? () => Symbol('ownerID') : () => ({}); | ||
export const getBatchToken = newOwnerID; | ||
function addOwnerID(obj, ownerID) { | ||
Object.defineProperty(obj, OWNER_ID_TAG, { | ||
value: ownerID, | ||
configurable: true, | ||
@@ -55,17 +43,9 @@ enumerable: false | ||
opts.batchManager.addMutated(obj); | ||
return obj; | ||
} | ||
function removeCanMutateTag(obj) { | ||
delete obj[MUTABILITY_TAG]; | ||
return obj; | ||
} | ||
function prepareNewObject(opts, instance) { | ||
if (opts.batchManager.isWithMutations()) { | ||
addCanMutateTag(opts, instance); | ||
function prepareNewObject(instance, ownerID) { | ||
if (ownerID) { | ||
addOwnerID(instance, ownerID); | ||
} | ||
opts.createdObjects++; | ||
return instance; | ||
@@ -81,3 +61,3 @@ } | ||
var PATH_SEPARATOR = '.'; | ||
const PATH_SEPARATOR = '.'; | ||
@@ -95,3 +75,3 @@ function normalizePath(pathArg) { | ||
function mutableSet(opts, key, value, obj) { | ||
function mutableSet(key, value, obj) { | ||
obj[key] = value; | ||
@@ -101,7 +81,7 @@ return obj; | ||
function mutableSetIn(opts, _pathArg, value, obj) { | ||
var originalPathArg = normalizePath(_pathArg); | ||
function mutableSetIn(_pathArg, value, obj) { | ||
const originalPathArg = normalizePath(_pathArg); | ||
var pathLen = originalPathArg.length; | ||
originalPathArg.reduce(function (acc, curr, idx) { | ||
const pathLen = originalPathArg.length; | ||
originalPathArg.reduce((acc, curr, idx) => { | ||
if (idx === pathLen - 1) { | ||
@@ -112,7 +92,7 @@ acc[curr] = value; | ||
var currType = typeof acc[curr]; | ||
const currType = typeof acc[curr]; | ||
if (currType === 'undefined') { | ||
var newObj = {}; | ||
prepareNewObject(opts, newObj); | ||
const newObj = {}; | ||
prepareNewObject(newObj, null); | ||
acc[curr] = newObj; | ||
@@ -126,4 +106,4 @@ return newObj; | ||
var pathRepr = originalPathArg[idx - 1] + '.' + curr; | ||
throw new Error('A non-object value was encountered when traversing setIn path at ' + pathRepr + '.'); | ||
const pathRepr = `${ originalPathArg[idx - 1] }.${ curr }`; | ||
throw new Error(`A non-object value was encountered when traversing setIn path at ${ pathRepr }.`); | ||
}); | ||
@@ -134,9 +114,9 @@ | ||
function valueInPath(opts, _pathArg, obj) { | ||
var pathArg = normalizePath(_pathArg); | ||
function valueInPath(_pathArg, obj) { | ||
const pathArg = normalizePath(_pathArg); | ||
var acc = obj; | ||
for (var i = 0; i < pathArg.length; i++) { | ||
var curr = pathArg[i]; | ||
var currRef = acc[curr]; | ||
let acc = obj; | ||
for (let i = 0; i < pathArg.length; i++) { | ||
const curr = pathArg[i]; | ||
const currRef = acc[curr]; | ||
if (i === pathArg.length - 1) { | ||
@@ -154,13 +134,20 @@ return currRef; | ||
function immutableSetIn(opts, _pathArg, value, obj) { | ||
var pathArg = normalizePath(_pathArg); | ||
function immutableSetIn(ownerID, _pathArg, value, obj) { | ||
const pathArg = normalizePath(_pathArg); | ||
var currentValue = valueInPath(opts, pathArg, obj); | ||
const currentValue = valueInPath(pathArg, obj); | ||
if (value === currentValue) return obj; | ||
var pathLen = pathArg.length; | ||
var acc = Object.assign(prepareNewObject(opts, {}), obj); | ||
var rootObj = acc; | ||
const pathLen = pathArg.length; | ||
pathArg.forEach(function (curr, idx) { | ||
let acc; | ||
if (canMutate(obj, ownerID)) { | ||
acc = obj; | ||
} else { | ||
acc = Object.assign(prepareNewObject({}, ownerID), obj); | ||
} | ||
const rootObj = acc; | ||
pathArg.forEach((curr, idx) => { | ||
if (idx === pathLen - 1) { | ||
@@ -171,10 +158,10 @@ acc[curr] = value; | ||
var currRef = acc[curr]; | ||
var currType = typeof currRef; | ||
const currRef = acc[curr]; | ||
const currType = typeof currRef; | ||
if (currType === 'object') { | ||
if (canMutate(currRef)) { | ||
if (canMutate(currRef, ownerID)) { | ||
acc = currRef; | ||
} else { | ||
var newObj = prepareNewObject(opts, {}); | ||
const newObj = prepareNewObject({}, ownerID); | ||
acc[curr] = Object.assign(newObj, currRef); | ||
@@ -187,3 +174,3 @@ acc = newObj; | ||
if (currType === 'undefined') { | ||
var newObj = prepareNewObject(opts, {}); | ||
const newObj = prepareNewObject({}, ownerID); | ||
acc[curr] = newObj; | ||
@@ -194,4 +181,4 @@ acc = newObj; | ||
var pathRepr = pathArg[idx - 1] + '.' + curr; | ||
throw new Error('A non-object value was encountered when traversing setIn path at ' + pathRepr + '.'); | ||
const pathRepr = `${ pathArg[idx - 1] }.${ curr }`; | ||
throw new Error(`A non-object value was encountered when traversing setIn path at ${ pathRepr }.`); | ||
}); | ||
@@ -202,15 +189,15 @@ | ||
function mutableMerge(isDeep, opts, _mergeObjs, baseObj) { | ||
var mergeObjs = forceArray(_mergeObjs); | ||
function mutableMerge(isDeep, _mergeObjs, baseObj) { | ||
const mergeObjs = forceArray(_mergeObjs); | ||
if (opts.deep) { | ||
mergeObjs.forEach(function (mergeObj) { | ||
(0, _lodashForOwn2['default'])(mergeObj, function (value, key) { | ||
if (isDeep) { | ||
mergeObjs.forEach(mergeObj => { | ||
forOwn(mergeObj, (value, key) => { | ||
if (isDeep && baseObj.hasOwnProperty(key)) { | ||
var assignValue = undefined; | ||
let assignValue; | ||
if (typeof value === 'object') { | ||
assignValue = canMutate(value) ? mutableMerge(isDeep, opts, [value], baseObj[key]) : immutableMerge(isDeep, opts, [value], baseObj[key]); // eslint-disable-line | ||
assignValue = mutableMerge(isDeep, [value], baseObj[key]); | ||
} else { | ||
assignValue = value; | ||
} | ||
assignValue = value; | ||
} | ||
@@ -224,3 +211,3 @@ baseObj[key] = assignValue; | ||
} else { | ||
Object.assign.apply(Object, [baseObj].concat(_toConsumableArray(mergeObjs))); | ||
Object.assign(baseObj, ...mergeObjs); | ||
} | ||
@@ -231,8 +218,8 @@ | ||
var mutableShallowMerge = mutableMerge.bind(null, false); | ||
var mutableDeepMerge = mutableMerge.bind(null, true); | ||
const mutableShallowMerge = mutableMerge.bind(null, false); | ||
const mutableDeepMerge = mutableMerge.bind(null, true); | ||
function mutableOmit(opts, _keys, obj) { | ||
var keys = forceArray(_keys); | ||
keys.forEach(function (key) { | ||
function mutableOmit(_keys, obj) { | ||
const keys = forceArray(_keys); | ||
keys.forEach(key => { | ||
delete obj[key]; | ||
@@ -247,24 +234,24 @@ }); | ||
function immutableMerge(isDeep, opts, _mergeObjs, obj) { | ||
if (canMutate(obj)) return mutableMerge(isDeep, opts, _mergeObjs, obj); | ||
var mergeObjs = forceArray(_mergeObjs); | ||
function immutableMerge(isDeep, ownerID, _mergeObjs, obj) { | ||
if (canMutate(obj, ownerID)) return mutableMerge(isDeep, _mergeObjs, obj); | ||
const mergeObjs = forceArray(_mergeObjs); | ||
var hasChanges = false; | ||
var nextObject = obj; | ||
let hasChanges = false; | ||
let nextObject = obj; | ||
var willChange = function willChange() { | ||
const willChange = () => { | ||
if (!hasChanges) { | ||
hasChanges = true; | ||
nextObject = Object.assign({}, obj); | ||
prepareNewObject(opts, nextObject); | ||
prepareNewObject(nextObject, ownerID); | ||
} | ||
}; | ||
mergeObjs.forEach(function (mergeObj) { | ||
(0, _lodashForOwn2['default'])(mergeObj, function (mergeValue, key) { | ||
mergeObjs.forEach(mergeObj => { | ||
forOwn(mergeObj, (mergeValue, key) => { | ||
if (isDeep && obj.hasOwnProperty(key)) { | ||
var currentValue = nextObject[key]; | ||
const currentValue = nextObject[key]; | ||
if (typeof mergeValue === 'object' && !(mergeValue instanceof Array)) { | ||
if (_shouldMergeKey(nextObject, mergeObj, key)) { | ||
var recursiveMergeResult = immutableMerge(isDeep, opts, mergeValue, currentValue); | ||
const recursiveMergeResult = immutableMerge(isDeep, ownerID, mergeValue, currentValue); | ||
@@ -289,13 +276,13 @@ if (recursiveMergeResult !== currentValue) { | ||
var immutableDeepMerge = immutableMerge.bind(null, true); | ||
var immutableShallowMerge = immutableMerge.bind(null, false); | ||
const immutableDeepMerge = immutableMerge.bind(null, true); | ||
const immutableShallowMerge = immutableMerge.bind(null, false); | ||
function immutableArrSet(opts, index, value, arr) { | ||
if (canMutate(arr)) return mutableSet(opts, index, value, arr); | ||
function immutableArrSet(ownerID, index, value, arr) { | ||
if (canMutate(arr, ownerID)) return mutableSet(index, value, arr); | ||
if (arr[index] === value) return arr; | ||
var newArr = fastArrayCopy(arr); | ||
const newArr = fastArrayCopy(arr); | ||
newArr[index] = value; | ||
prepareNewObject(opts, newArr); | ||
prepareNewObject(newArr, ownerID); | ||
@@ -305,10 +292,10 @@ return newArr; | ||
function immutableSet(opts, key, value, obj) { | ||
if ((0, _lodashIsArrayLike2['default'])(obj)) return immutableArrSet(opts, key, value, obj); | ||
if (canMutate(obj)) return mutableSet(opts, key, value, obj); | ||
function immutableSet(ownerID, key, value, obj) { | ||
if (isArrayLike(obj)) return immutableArrSet(ownerID, key, value, obj); | ||
if (canMutate(obj, ownerID)) return mutableSet(key, value, obj); | ||
if (obj[key] === value) return obj; | ||
var newObj = Object.assign({}, obj); | ||
prepareNewObject(opts, newObj); | ||
const newObj = Object.assign({}, obj); | ||
prepareNewObject(newObj, ownerID); | ||
newObj[key] = value; | ||
@@ -318,9 +305,7 @@ return newObj; | ||
function immutableOmit(opts, _keys, obj) { | ||
if (canMutate(obj)) return mutableOmit(opts, _keys, obj); | ||
function immutableOmit(ownerID, _keys, obj) { | ||
if (canMutate(obj, ownerID)) return mutableOmit(_keys, obj); | ||
var keys = forceArray(_keys); | ||
var keysInObj = keys.filter(function (key) { | ||
return obj.hasOwnProperty(key); | ||
}); | ||
const keys = forceArray(_keys); | ||
const keysInObj = keys.filter(key => obj.hasOwnProperty(key)); | ||
@@ -330,21 +315,21 @@ // None of the keys were in the object, so we can return `obj`. | ||
var newObj = Object.assign({}, obj); | ||
keysInObj.forEach(function (key) { | ||
const newObj = Object.assign({}, obj); | ||
keysInObj.forEach(key => { | ||
delete newObj[key]; | ||
}); | ||
prepareNewObject(opts, newObj); | ||
prepareNewObject(newObj, ownerID); | ||
return newObj; | ||
} | ||
function mutableArrPush(opts, _vals, arr) { | ||
var vals = forceArray(_vals); | ||
arr.push.apply(arr, _toConsumableArray(vals)); | ||
function mutableArrPush(_vals, arr) { | ||
const vals = forceArray(_vals); | ||
arr.push(...vals); | ||
return arr; | ||
} | ||
function mutableArrFilter(opts, func, arr) { | ||
var currIndex = 0; | ||
var originalIndex = 0; | ||
function mutableArrFilter(func, arr) { | ||
let currIndex = 0; | ||
let originalIndex = 0; | ||
while (currIndex < arr.length) { | ||
var item = arr[currIndex]; | ||
const item = arr[currIndex]; | ||
if (!func(item, originalIndex)) { | ||
@@ -361,19 +346,19 @@ arr.splice(currIndex, 1); | ||
function mutableArrSplice(opts, index, deleteCount, _vals, arr) { | ||
var vals = forceArray(_vals); | ||
arr.splice.apply(arr, [index, deleteCount].concat(_toConsumableArray(vals))); | ||
function mutableArrSplice(index, deleteCount, _vals, arr) { | ||
const vals = forceArray(_vals); | ||
arr.splice(index, deleteCount, ...vals); | ||
return arr; | ||
} | ||
function mutableArrInsert(opts, index, _vals, arr) { | ||
return mutableArrSplice(opts, index, 0, _vals, arr); | ||
function mutableArrInsert(index, _vals, arr) { | ||
return mutableArrSplice(index, 0, _vals, arr); | ||
} | ||
function immutableArrSplice(opts, index, deleteCount, _vals, arr) { | ||
if (canMutate(arr)) return mutableArrSplice(opts, index, deleteCount, _vals, arr); | ||
function immutableArrSplice(ownerID, index, deleteCount, _vals, arr) { | ||
if (canMutate(arr, ownerID)) return mutableArrSplice(index, deleteCount, _vals, arr); | ||
var vals = forceArray(_vals); | ||
var newArr = arr.slice(); | ||
prepareNewObject(opts, newArr); | ||
newArr.splice.apply(newArr, [index, deleteCount].concat(_toConsumableArray(vals))); | ||
const vals = forceArray(_vals); | ||
const newArr = arr.slice(); | ||
prepareNewObject(newArr, ownerID); | ||
newArr.splice(index, deleteCount, ...vals); | ||
@@ -383,22 +368,22 @@ return newArr; | ||
function immutableArrInsert(opts, index, _vals, arr) { | ||
if (canMutate(arr)) return mutableArrInsert(opts, index, _vals, arr); | ||
return immutableArrSplice(opts, index, 0, _vals, arr); | ||
function immutableArrInsert(ownerID, index, _vals, arr) { | ||
if (canMutate(arr, ownerID)) return mutableArrInsert(index, _vals, arr); | ||
return immutableArrSplice(ownerID, index, 0, _vals, arr); | ||
} | ||
function immutableArrPush(opts, vals, arr) { | ||
return immutableArrInsert(opts, arr.length, vals, arr); | ||
function immutableArrPush(ownerID, vals, arr) { | ||
return immutableArrInsert(ownerID, arr.length, vals, arr); | ||
} | ||
function immutableArrFilter(opts, func, arr) { | ||
if (canMutate(arr)) return mutableArrFilter(opts, func, arr); | ||
var newArr = arr.filter(func); | ||
function immutableArrFilter(ownerID, func, arr) { | ||
if (canMutate(arr, ownerID)) return mutableArrFilter(func, arr); | ||
const newArr = arr.filter(func); | ||
if (newArr.length === arr.length) return arr; | ||
prepareNewObject(opts, newArr); | ||
prepareNewObject(newArr, ownerID); | ||
return newArr; | ||
} | ||
var operations = { | ||
const immutableOperations = { | ||
// object operations | ||
@@ -417,123 +402,68 @@ merge: immutableShallowMerge, | ||
// both | ||
set: immutableSet, | ||
set: immutableSet | ||
}; | ||
mutable: { | ||
// object operations | ||
merge: mutableShallowMerge, | ||
deepMerge: mutableDeepMerge, | ||
omit: mutableOmit, | ||
setIn: mutableSetIn, | ||
const mutableOperations = { | ||
// object operations | ||
merge: mutableShallowMerge, | ||
deepMerge: mutableDeepMerge, | ||
omit: mutableOmit, | ||
setIn: mutableSetIn, | ||
// array operations | ||
insert: mutableArrInsert, | ||
push: mutableArrPush, | ||
filter: mutableArrFilter, | ||
splice: mutableArrSplice, | ||
// array operations | ||
insert: mutableArrInsert, | ||
push: mutableArrPush, | ||
filter: mutableArrFilter, | ||
splice: mutableArrSplice, | ||
// both | ||
set: mutableSet | ||
} | ||
// both | ||
set: mutableSet | ||
}; | ||
function bindOperationsToOptions(opsObj, opts) { | ||
var boundOperations = {}; | ||
export function getImmutableOps() { | ||
const immutableOps = Object.assign({}, immutableOperations); | ||
forOwn(immutableOps, (value, key) => { | ||
immutableOps[key] = curry(value.bind(null, null)); | ||
}); | ||
(0, _lodashForOwn2['default'])(opsObj, function (value, key) { | ||
if (typeof value === 'object') { | ||
boundOperations[key] = bindOperationsToOptions(value, opts); | ||
} else { | ||
boundOperations[key] = value.bind(null, opts); | ||
const mutableOps = Object.assign({}, mutableOperations); | ||
forOwn(mutableOps, (value, key) => { | ||
mutableOps[key] = curry(value); | ||
}); | ||
if (opts.curried) { | ||
boundOperations[key] = (0, _ramdaSrcCurry2['default'])(boundOperations[key]); | ||
} | ||
} | ||
const batchOps = Object.assign({}, immutableOperations); | ||
forOwn(batchOps, (value, key) => { | ||
batchOps[key] = curry(value); | ||
}); | ||
return boundOperations; | ||
} | ||
function batched(_token, _fn) { | ||
let token; | ||
let fn; | ||
function getBatchManager() { | ||
var previousSessionStack = []; | ||
var currMutatedObjects = null; | ||
var objectsCreated = 0; | ||
return { | ||
open: function open() { | ||
if (currMutatedObjects !== null) { | ||
previousSessionStack.push(currMutatedObjects); | ||
} | ||
currMutatedObjects = []; | ||
}, | ||
isWithMutations: function isWithMutations() { | ||
return currMutatedObjects !== null; | ||
}, | ||
addMutated: function addMutated(obj) { | ||
currMutatedObjects.push(obj); | ||
objectsCreated++; | ||
}, | ||
getMutatedObjects: function getMutatedObjects() { | ||
return currMutatedObjects; | ||
}, | ||
getObjectsCreatedCount: function getObjectsCreatedCount() { | ||
return objectsCreated; | ||
}, | ||
close: function close() { | ||
if (currMutatedObjects !== null) { | ||
currMutatedObjects.forEach(removeCanMutateTag); | ||
if (previousSessionStack.length) { | ||
currMutatedObjects = previousSessionStack.pop(); | ||
} else { | ||
currMutatedObjects = null; | ||
} | ||
objectsCreated = 0; | ||
} | ||
if (typeof _token === 'function') { | ||
fn = _token; | ||
token = getBatchToken(); | ||
} else { | ||
token = _token; | ||
fn = _fn; | ||
} | ||
}; | ||
} | ||
function getImmutableOps(userOpts) { | ||
var defaultOpts = { | ||
curried: true, | ||
batchManager: getBatchManager() | ||
}; | ||
var opts = Object.assign({ createdObjects: 0 }, defaultOpts, userOpts || {}); | ||
var boundOperations = bindOperationsToOptions(operations, opts); | ||
function batchWrapper() { | ||
var func = arguments[0]; | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
opts.batchManager.open(); | ||
var returnValue = func.apply(null, args); | ||
opts.batchManager.close(); | ||
return returnValue; | ||
const immutableOpsBoundToToken = Object.assign({}, immutableOperations); | ||
forOwn(immutableOpsBoundToToken, (value, key) => { | ||
immutableOpsBoundToToken[key] = curry(value.bind(null, token)); | ||
}); | ||
return fn(immutableOpsBoundToToken); | ||
} | ||
boundOperations.batched = batchWrapper; | ||
boundOperations.batch = (0, _ramdaSrcWrap2['default'])(_ramdaSrc__2['default'], batchWrapper); | ||
boundOperations.createdObjectsCount = function () { | ||
return opts.createdObjects; | ||
}; | ||
boundOperations.getMutatedObjects = opts.batchManager.getMutatedObjects; | ||
boundOperations.__ = _ramdaSrc__2['default']; | ||
boundOperations.open = opts.batchManager.open; | ||
boundOperations.close = opts.batchManager.close; | ||
boundOperations.getBatchManager = getBatchManager; | ||
return Object.assign(immutableOps, { | ||
mutable: mutableOps, | ||
batch: batchOps, | ||
batched, | ||
__: placeholder, | ||
getBatchToken | ||
}); | ||
} | ||
boundOperations.useBatchManager = function (manager) { | ||
opts.batchManager.close(); | ||
opts.batchManager = manager; | ||
boundOperations.open = manager.open; | ||
boundOperations.close = manager.close; | ||
boundOperations.getMutatedObjects = manager.getMutatedObjects; | ||
}; | ||
export const ops = getImmutableOps(); | ||
return boundOperations; | ||
} | ||
export default ops; |
@@ -1,66 +0,34 @@ | ||
'use strict'; | ||
import chai from 'chai'; | ||
import sinonChai from 'sinon-chai'; | ||
import freeze from 'deep-freeze'; | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } | ||
import { ops, canMutate, getBatchToken } from '../index'; | ||
var _chai = require('chai'); | ||
chai.use(sinonChai); | ||
const { expect } = chai; | ||
var _chai2 = _interopRequireDefault(_chai); | ||
describe('batched', () => { | ||
it('works', () => { | ||
const res = ops.batched(batchOps => { | ||
const obj = {}; | ||
const result = batchOps.set('a', 1, obj); | ||
expect(result).to.deep.equal({ a: 1 }); | ||
expect(result).not.to.equal(obj); | ||
var _sinonChai = require('sinon-chai'); | ||
var _sinonChai2 = _interopRequireDefault(_sinonChai); | ||
var _index = require('../index'); | ||
var _index2 = _interopRequireDefault(_index); | ||
var _deepFreeze = require('deep-freeze'); | ||
var _deepFreeze2 = _interopRequireDefault(_deepFreeze); | ||
var _ramdaSrcCompose = require('ramda/src/compose'); | ||
var _ramdaSrcCompose2 = _interopRequireDefault(_ramdaSrcCompose); | ||
_chai2['default'].use(_sinonChai2['default']); | ||
var expect = _chai2['default'].expect; | ||
describe('operations', function () { | ||
var ops = undefined; | ||
beforeEach(function () { | ||
ops = (0, _index2['default'])(); | ||
}); | ||
it('wrapBatched', function () { | ||
var pushFour = ops.push(4); | ||
var pushFive = ops.push(5); | ||
var arr = (0, _deepFreeze2['default'])([1, 2, 3]); | ||
var pusher = ops.batch((0, _ramdaSrcCompose2['default'])(pushFive, pushFour)); | ||
expect(pusher).to.be.a('function'); | ||
var result = pusher(arr); | ||
expect(result).to.deep.equal([1, 2, 3, 4, 5]); | ||
}); | ||
it('useBatchManager', function () { | ||
var myManager = ops.getBatchManager(); | ||
ops.useBatchManager(myManager); | ||
var pusher = ops.push(0); | ||
var batchOperation = ops.batch(function (arr) { | ||
var first = pusher(arr); | ||
expect(ops.getMutatedObjects()).to.equal(myManager.getMutatedObjects()); | ||
expect(ops.getMutatedObjects()[0]).to.equal(first); | ||
return first; | ||
const result2 = batchOps.omit('a', result); | ||
expect(result2).to.equal(result); | ||
expect(result2).to.deep.equal({}); | ||
return result2; | ||
}); | ||
batchOperation([]); | ||
expect(res).to.deep.equal({}); | ||
}); | ||
}); | ||
describe('object', function () { | ||
describe('batched mutations', function () { | ||
it('deepMerges', function () { | ||
var baseObj = (0, _deepFreeze2['default'])({ | ||
describe('operations', () => { | ||
describe('object', () => { | ||
describe('batched mutations', () => { | ||
const token = getBatchToken(); | ||
it('deepMerges', () => { | ||
const baseObj = freeze({ | ||
change: 'Tommi', | ||
@@ -73,3 +41,3 @@ dontChange: 25, | ||
}); | ||
var mergeObj = (0, _deepFreeze2['default'])({ | ||
const mergeObj = freeze({ | ||
change: 'None', | ||
@@ -82,12 +50,9 @@ add: 'US', | ||
}); | ||
var result = undefined; | ||
var merger = ops.deepMerge(mergeObj); | ||
ops.batched(function () { | ||
result = merger(baseObj); | ||
expect((0, _index.canMutate)(result)).to.be['true']; | ||
expect((0, _index.canMutate)(result.deeper)).to.be['true']; | ||
}); | ||
const merger = ops.batch.deepMerge(token, mergeObj); | ||
const result = merger(baseObj); | ||
expect(canMutate(result, token)).to.be.true; | ||
expect(canMutate(result.deeper, token)).to.be.true; | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
expect((0, _index.canMutate)(result.deeper)).to.be['false']; | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(canMutate(result.deeper, getBatchToken())).to.be.false; | ||
@@ -106,4 +71,4 @@ expect(result).to.not.equal(baseObj); | ||
it('omits a single key', function () { | ||
var obj = (0, _deepFreeze2['default'])({ | ||
it('omits a single key', () => { | ||
const obj = freeze({ | ||
name: 'Tommi', | ||
@@ -113,16 +78,16 @@ age: 25 | ||
var result = undefined; | ||
var omitter = ops.omit('age'); | ||
const omitter = ops.batch.omit(token, 'age'); | ||
ops.batched(function () { | ||
result = omitter(obj); | ||
expect((0, _index.canMutate)(result)).to.be['true']; | ||
}); | ||
const result = omitter(obj); | ||
expect(canMutate(result, token)).to.be.true; | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(result).to.not.contain.keys(['age']); | ||
// Further modification should mutate the existing object. | ||
expect(ops.batch.omit(token, 'name', result)).to.equal(result); | ||
}); | ||
it('omits an array of keys', function () { | ||
var obj = (0, _deepFreeze2['default'])({ | ||
it('omits an array of keys', () => { | ||
const obj = freeze({ | ||
name: 'Tommi', | ||
@@ -132,17 +97,16 @@ age: 25 | ||
var result = undefined; | ||
const omitter = ops.batch.omit(token, ['age']); | ||
const result = omitter(obj); | ||
var omitter = ops.omit(['age']); | ||
ops.batched(function () { | ||
result = omitter(obj); | ||
expect(canMutate(result, token)).to.be.true; | ||
expect((0, _index.canMutate)(result)).to.be['true']; | ||
}); | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(result).to.not.contain.keys(['age']); | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
expect(result).to.not.contain.keys(['age']); | ||
// Further modification should mutate the existing object. | ||
expect(ops.batch.omit(token, ['name'], result)).to.equal(result); | ||
}); | ||
it('sets a value', function () { | ||
var obj = (0, _deepFreeze2['default'])({ | ||
it('sets a value', () => { | ||
const obj = freeze({ | ||
one: 1, | ||
@@ -153,10 +117,7 @@ two: 500, | ||
var result = undefined; | ||
ops.batched(function () { | ||
result = ops.set('two', 5, obj); | ||
const result = ops.batch.set(token, 'two', 5, obj); | ||
expect((0, _index.canMutate)(result)).to.be['true']; | ||
result = ops.set('two', 2, result); | ||
}); | ||
expect(result).to.deep.equal({ | ||
expect(canMutate(result, token)).to.be.true; | ||
const result2 = ops.batch.set(token, 'two', 2, result); | ||
expect(result2).to.deep.equal({ | ||
one: 1, | ||
@@ -166,6 +127,8 @@ two: 2, | ||
}); | ||
expect(result).to.equal(result2); | ||
}); | ||
it('sets a value in path', function () { | ||
var obj = (0, _deepFreeze2['default'])({ | ||
it('sets a value in path', () => { | ||
const obj = freeze({ | ||
first: { | ||
@@ -180,24 +143,24 @@ second: { | ||
}); | ||
var result = undefined; | ||
var setter = ops.setIn('first.second.value', 'anotherValue'); | ||
const setter = ops.batch.setIn(token, 'first.second.value', 'anotherValue'); | ||
ops.batched(function () { | ||
result = setter(obj); | ||
const result = setter(obj); | ||
expect(canMutate(result, token)).to.be.true; | ||
expect((0, _index.canMutate)(result)).to.be['true']; | ||
}); | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(result).not.to.equal(obj); | ||
expect(result.first.second.value).to.equal('anotherValue'); | ||
expect(result.maintain).to.be['true']; | ||
expect(result.first.maintain).to.be['true']; | ||
expect(result.first.second.maintain).to.be['true']; | ||
expect(result.maintain).to.be.true; | ||
expect(result.first.maintain).to.be.true; | ||
expect(result.first.second.maintain).to.be.true; | ||
const result2 = ops.batch.setIn(token, 'first.second.value', 'secondAnotherValue', result); | ||
expect(result).to.equal(result2); | ||
expect(result2.first.second.value).to.equal('secondAnotherValue'); | ||
}); | ||
}); | ||
describe('immutable ops', function () { | ||
it('deepMerges', function () { | ||
var baseObj = (0, _deepFreeze2['default'])({ | ||
describe('immutable ops', () => { | ||
it('deepMerges', () => { | ||
const baseObj = freeze({ | ||
change: 'Tommi', | ||
@@ -210,3 +173,3 @@ dontChange: 25, | ||
}); | ||
var mergeObj = (0, _deepFreeze2['default'])({ | ||
const mergeObj = freeze({ | ||
change: 'None', | ||
@@ -220,7 +183,7 @@ add: 'US', | ||
var merger = ops.deepMerge(mergeObj); | ||
var result = merger(baseObj); | ||
const merger = ops.deepMerge(mergeObj); | ||
const result = merger(baseObj); | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
expect((0, _index.canMutate)(result.deeper)).to.be['false']; | ||
expect(canMutate(result)).to.be.false; | ||
expect(canMutate(result.deeper)).to.be.false; | ||
@@ -239,4 +202,4 @@ expect(result).to.not.equal(baseObj); | ||
it('deepMerges and returns initial object when no values changed', function () { | ||
var baseObj = (0, _deepFreeze2['default'])({ | ||
it('deepMerges and returns initial object when no values changed', () => { | ||
const baseObj = freeze({ | ||
deep: { | ||
@@ -246,3 +209,3 @@ dontChange: 'John' | ||
}); | ||
var mergeObj = (0, _deepFreeze2['default'])({ | ||
const mergeObj = freeze({ | ||
deep: { | ||
@@ -253,8 +216,8 @@ dontChange: 'John' | ||
var result = ops.deepMerge(mergeObj, baseObj); | ||
const result = ops.deepMerge(mergeObj, baseObj); | ||
expect(result).to.equal(baseObj); | ||
}); | ||
it('omits a single key', function () { | ||
var obj = (0, _deepFreeze2['default'])({ | ||
it('omits a single key', () => { | ||
const obj = freeze({ | ||
name: 'Tommi', | ||
@@ -264,11 +227,11 @@ age: 25 | ||
var omitter = ops.omit('age'); | ||
var result = omitter(obj); | ||
const omitter = ops.omit('age'); | ||
const result = omitter(obj); | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
expect(canMutate(result)).to.be.false; | ||
expect(result).to.not.contain.keys(['age']); | ||
}); | ||
it('omits a single key, returns same object if no value changes', function () { | ||
var obj = (0, _deepFreeze2['default'])({ | ||
it('omits a single key, returns same object if no value changes', () => { | ||
const obj = freeze({ | ||
name: 'Tommi', | ||
@@ -278,8 +241,8 @@ age: 25 | ||
var result = ops.omit('location', obj); | ||
const result = ops.omit('location', obj); | ||
expect(result).to.equal(obj); | ||
}); | ||
it('omits an array of keys', function () { | ||
var obj = (0, _deepFreeze2['default'])({ | ||
it('omits an array of keys', () => { | ||
const obj = freeze({ | ||
name: 'Tommi', | ||
@@ -289,11 +252,11 @@ age: 25 | ||
var omitter = ops.omit(['age']); | ||
var result = omitter(obj); | ||
const omitter = ops.omit(['age']); | ||
const result = omitter(obj); | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
expect(canMutate(result)).to.be.false; | ||
expect(result).to.not.contain.keys(['age']); | ||
}); | ||
it('sets a value', function () { | ||
var obj = (0, _deepFreeze2['default'])({ | ||
it('sets a value', () => { | ||
const obj = freeze({ | ||
name: 'Tommi', | ||
@@ -303,3 +266,3 @@ age: 25 | ||
var result = ops.set('age', 26, obj); | ||
const result = ops.set('age', 26, obj); | ||
expect(result).to.deep.equal({ | ||
@@ -311,4 +274,4 @@ name: 'Tommi', | ||
it('sets a value and returns the initial value of no changes', function () { | ||
var obj = (0, _deepFreeze2['default'])({ | ||
it('sets a value and returns the initial value of no changes', () => { | ||
const obj = freeze({ | ||
name: 'Tommi', | ||
@@ -318,8 +281,8 @@ age: 25 | ||
var result = ops.set('age', 25, obj); | ||
const result = ops.set('age', 25, obj); | ||
expect(result).to.equal(obj); | ||
}); | ||
it('sets a value in path', function () { | ||
var obj = (0, _deepFreeze2['default'])({ | ||
it('sets a value in path', () => { | ||
const obj = freeze({ | ||
first: { | ||
@@ -335,16 +298,16 @@ second: { | ||
var setter = ops.setIn('first.second.value', 'anotherValue'); | ||
const setter = ops.setIn('first.second.value', 'anotherValue'); | ||
var result = setter(obj); | ||
const result = setter(obj); | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
expect(canMutate(result)).to.be.false; | ||
expect(result).not.to.equal(obj); | ||
expect(result.first.second.value).to.equal('anotherValue'); | ||
expect(result.maintain).to.be['true']; | ||
expect(result.first.maintain).to.be['true']; | ||
expect(result.first.second.maintain).to.be['true']; | ||
expect(result.maintain).to.be.true; | ||
expect(result.first.maintain).to.be.true; | ||
expect(result.first.second.maintain).to.be.true; | ||
}); | ||
it('sets a value in path but returns same object if no value changes', function () { | ||
var obj = (0, _deepFreeze2['default'])({ | ||
it('sets a value in path but returns same object if no value changes', () => { | ||
const obj = freeze({ | ||
first: { | ||
@@ -360,3 +323,3 @@ second: { | ||
var result = ops.setIn('first.second.value', 'value', obj); | ||
const result = ops.setIn('first.second.value', 'value', obj); | ||
expect(result).to.equal(obj); | ||
@@ -367,88 +330,97 @@ }); | ||
describe('array', function () { | ||
describe('batched mutations', function () { | ||
it('push', function () { | ||
var push = ops.push; | ||
var arr = (0, _deepFreeze2['default'])([5, 4]); | ||
var pusher = push((0, _deepFreeze2['default'])([1, 2, 3])); | ||
var result = ops.batched(function () { | ||
return pusher(arr); | ||
}); | ||
describe('array', () => { | ||
describe('batched mutations', () => { | ||
const token = getBatchToken(); | ||
it('push', () => { | ||
const push = ops.batch.push; | ||
const arr = freeze([5, 4]); | ||
const pusher = push(token, freeze([1, 2, 3])); | ||
const result = pusher(arr); | ||
expect(result).to.not.equal(arr); | ||
expect(result).to.deep.equal([5, 4, 1, 2, 3]); | ||
const result2 = push(token, [4, 5], result); | ||
expect(result).to.equal(result2); | ||
expect(result2).to.deep.equal([5, 4, 1, 2, 3, 4, 5]); | ||
}); | ||
it('insert', function () { | ||
var insert = ops.insert; | ||
var arr = (0, _deepFreeze2['default'])([1, 2, 5]); | ||
var inserter = insert(2, (0, _deepFreeze2['default'])([3, 4])); | ||
var result = ops.batched(function () { | ||
return inserter(arr); | ||
}); | ||
it('insert', () => { | ||
const insert = ops.batch.insert; | ||
const arr = freeze([1, 2, 5]); | ||
const inserter = insert(token, 2, freeze([3, 4])); | ||
const result = inserter(arr); | ||
expect(result).to.deep.equal([1, 2, 3, 4, 5]); | ||
const result2 = ops.batch.insert(token, 2, [1000], result); | ||
expect(result).to.equal(result2); | ||
expect(result2).to.deep.equal([1, 2, 1000, 3, 4, 5]); | ||
}); | ||
it('filter', function () { | ||
var arr = (0, _deepFreeze2['default'])([0, 1, 2, 3]); | ||
var result = undefined; | ||
it('filter', () => { | ||
const arr = freeze([0, 1, 2, 3]); | ||
const result = ops.batch.filter(token, item => item % 2 === 0, arr); | ||
expect(canMutate(result, token)).to.be.true; | ||
ops.batched(function () { | ||
result = ops.filter(function (item) { | ||
return item % 2 === 0; | ||
}, arr); | ||
expect((0, _index.canMutate)(result)).to.be['true']; | ||
}); | ||
expect(result).to.deep.equal([0, 2]); | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(result).to.deep.equal([0, 2]); | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
const result2 = ops.batch.filter(token, item => item === 2, result); | ||
expect(result2).to.equal(result); | ||
expect(result2).to.deep.equal([2]); | ||
}); | ||
it('set', function () { | ||
var arr = (0, _deepFreeze2['default'])([1, 2, 987, 4]); | ||
it('set', () => { | ||
const arr = freeze([1, 2, 987, 4]); | ||
var result = ops.batched(function () { | ||
var setter = ops.set(2, 3); | ||
var res = setter(arr); | ||
expect((0, _index.canMutate)(res)).to.be['true']; | ||
return res; | ||
}); | ||
const setter = ops.batch.set(token, 2, 3); | ||
const result = setter(arr); | ||
expect(canMutate(result, token)).to.be.true; | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(result).to.deep.equal([1, 2, 3, 4]); | ||
const result2 = ops.batch.set(token, 2, 1000, result); | ||
expect(result).to.equal(result2); | ||
expect(result2).to.deep.equal([1, 2, 1000, 4]); | ||
}); | ||
it('splice with deletions', function () { | ||
var splice = ops.splice; | ||
var arr = (0, _deepFreeze2['default'])([1, 2, 3, 3, 3, 4]); | ||
var splicer = splice(2, 2, []); | ||
it('splice with deletions', () => { | ||
const splice = ops.batch.splice; | ||
const arr = freeze([1, 2, 3, 3, 3, 4]); | ||
const splicer = splice(token, 2, 2, []); | ||
var result = ops.batched(function () { | ||
return splicer(arr); | ||
}); | ||
const result = splicer(arr); | ||
expect(result).to.deep.equal([1, 2, 3, 4]); | ||
const result2 = ops.batch.splice(token, 2, 1, [], result); | ||
expect(result2).to.equal(result); | ||
expect(result2).to.deep.equal([1, 2, 4]); | ||
}); | ||
it('splice with additions', function () { | ||
var splice = ops.splice; | ||
var arr = (0, _deepFreeze2['default'])([1, 5]); | ||
var splicer = splice(1, 0, [2, 3, 4]); | ||
it('splice with additions', () => { | ||
const splice = ops.batch.splice; | ||
const arr = freeze([1, 5]); | ||
const splicer = splice(token, 1, 0, [2, 3, 4]); | ||
var result = ops.batched(function () { | ||
return splicer(arr); | ||
}); | ||
const result = splicer(arr); | ||
expect(result).to.deep.equal([1, 2, 3, 4, 5]); | ||
const result2 = ops.batch.splice(token, 0, 1, [1000], result); | ||
expect(result).to.equal(result2); | ||
expect(result2).to.deep.equal([1000, 2, 3, 4, 5]); | ||
}); | ||
}); | ||
describe('immutable ops', function () { | ||
it('push', function () { | ||
var push = ops.push; | ||
var arr = (0, _deepFreeze2['default'])([5, 4]); | ||
var pusher = push((0, _deepFreeze2['default'])([1, 2, 3])); | ||
var result = pusher(arr); | ||
describe('immutable ops', () => { | ||
it('push', () => { | ||
const push = ops.push; | ||
const arr = freeze([5, 4]); | ||
const pusher = push(freeze([1, 2, 3])); | ||
const result = pusher(arr); | ||
@@ -460,7 +432,7 @@ expect(result).to.not.equal(arr); | ||
it('insert', function () { | ||
var insert = ops.insert; | ||
var arr = (0, _deepFreeze2['default'])([1, 2, 5]); | ||
var inserter = insert(2, (0, _deepFreeze2['default'])([3, 4])); | ||
var result = inserter(arr); | ||
it('insert', () => { | ||
const insert = ops.insert; | ||
const arr = freeze([1, 2, 5]); | ||
const inserter = insert(2, freeze([3, 4])); | ||
const result = inserter(arr); | ||
@@ -470,52 +442,48 @@ expect(result).to.deep.equal([1, 2, 3, 4, 5]); | ||
it('filter', function () { | ||
var arr = (0, _deepFreeze2['default'])([0, 1, 2, 3]); | ||
it('filter', () => { | ||
const arr = freeze([0, 1, 2, 3]); | ||
var result = ops.filter(function (item) { | ||
return item % 2 === 0; | ||
}, arr); | ||
const result = ops.filter(item => item % 2 === 0, arr); | ||
expect(result).to.deep.equal([0, 2]); | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
expect(canMutate(result)).to.be.false; | ||
}); | ||
it('filter with no effect should return initial array', function () { | ||
var arr = (0, _deepFreeze2['default'])([0, 1, 2, 3]); | ||
var result = ops.filter(function (item) { | ||
return item < 4; | ||
}, arr); | ||
it('filter with no effect should return initial array', () => { | ||
const arr = freeze([0, 1, 2, 3]); | ||
const result = ops.filter(item => item < 4, arr); | ||
expect(result).to.equal(arr); | ||
}); | ||
it('set', function () { | ||
var arr = (0, _deepFreeze2['default'])([1, 2, 987, 4]); | ||
it('set', () => { | ||
const arr = freeze([1, 2, 987, 4]); | ||
var result = ops.set(2, 3, arr); | ||
const result = ops.set(2, 3, arr); | ||
expect((0, _index.canMutate)(result)).to.be['false']; | ||
expect(canMutate(result)).to.be.false; | ||
expect(result).to.deep.equal([1, 2, 3, 4]); | ||
}); | ||
it('set with no effect should return initial array', function () { | ||
var arr = (0, _deepFreeze2['default'])([1, 2, 3, 4]); | ||
it('set with no effect should return initial array', () => { | ||
const arr = freeze([1, 2, 3, 4]); | ||
var result = ops.set(2, 3, arr); | ||
const result = ops.set(2, 3, arr); | ||
expect(result).to.equal(arr); | ||
}); | ||
it('splice with deletions', function () { | ||
var splice = ops.splice; | ||
var arr = (0, _deepFreeze2['default'])([1, 2, 3, 3, 3, 4]); | ||
var splicer = splice(2, 2, []); | ||
it('splice with deletions', () => { | ||
const splice = ops.splice; | ||
const arr = freeze([1, 2, 3, 3, 3, 4]); | ||
const splicer = splice(2, 2, []); | ||
var result = splicer(arr); | ||
const result = splicer(arr); | ||
expect(result).to.deep.equal([1, 2, 3, 4]); | ||
}); | ||
it('splice with additions', function () { | ||
var splice = ops.splice; | ||
var arr = (0, _deepFreeze2['default'])([1, 5]); | ||
var splicer = splice(1, 0, [2, 3, 4]); | ||
it('splice with additions', () => { | ||
const splice = ops.splice; | ||
const arr = freeze([1, 5]); | ||
const splicer = splice(1, 0, [2, 3, 4]); | ||
var result = splicer(arr); | ||
const result = splicer(arr); | ||
@@ -522,0 +490,0 @@ expect(result).to.deep.equal([1, 2, 3, 4, 5]); |
{ | ||
"name": "immutable-ops", | ||
"version": "0.4.2", | ||
"version": "0.5.0", | ||
"description": "A collection of functions to perform immutable operations on plain JavaScript objects", | ||
@@ -18,17 +18,17 @@ "main": "lib/index.js", | ||
"devDependencies": { | ||
"babel": "^5.8.24", | ||
"babel-core": "^5.8.24", | ||
"babel-eslint": "^4.1.5", | ||
"chai": "^3.0.0", | ||
"babel-cli": "^6.18.0", | ||
"babel-core": "^6.18.2", | ||
"babel-eslint": "^7.1.0", | ||
"chai": "^3.5.0", | ||
"deep-freeze": "0.0.1", | ||
"eslint": "^1.10.1", | ||
"eslint-config-airbnb": "1.0.0", | ||
"mocha": "^2.2.5", | ||
"sinon": "^1.17.2", | ||
"eslint": "^3.10.0", | ||
"eslint-config-airbnb-base": "10.0.1", | ||
"eslint-plugin-import": "^2.2.0", | ||
"mocha": "^3.1.2", | ||
"sinon": "^1.17.6", | ||
"sinon-chai": "^2.8.0" | ||
}, | ||
"dependencies": { | ||
"lodash": "^4.2.1", | ||
"ramda": "^0.19.1" | ||
"ramda": "^0.22.1" | ||
} | ||
} |
113
README.md
@@ -27,3 +27,3 @@ immutable-ops | ||
import compose from 'ramda/src/compose'; | ||
import getOps from 'immutable-ops'; | ||
import ops from 'immutable-ops'; | ||
@@ -47,19 +47,5 @@ // These are all the available functions. | ||
// Batch mutations | ||
// Run a function batched | ||
batched, | ||
// Wrap a function to be executed as a batch | ||
batch, | ||
// Open a batch | ||
open, | ||
// Close a batch | ||
close, | ||
// Placeholder for currying. | ||
__, | ||
} = getOps({ | ||
// These are the default options. | ||
curried: true | ||
}); | ||
} = ops; | ||
@@ -79,12 +65,18 @@ const arr = [1, 2, 3]; | ||
const result = pushFourAndFive(arr); | ||
// Two new arrays were created during `pushFourAndFive` eecution. | ||
// Two new arrays were created during `pushFourAndFive` execution. | ||
expect(result).to.deep.equal([1, 2, 3, 4, 5]); | ||
const batchedPushFourAndFive = ops.batch(pushFourAndFive); | ||
const sameResult = batchedPushFourAndFive(arr); | ||
// Only one new array is created during `batchedPushFourAndFive` execution. | ||
// `immutable-ops` keeps track of objects mutated during the wrapped | ||
// function, and applies the same operations with mutations to those objects. | ||
expect(result).to.deep.equal([1, 2, 3, 4, 5]); | ||
// Only one new array is created. | ||
const sameResult = ops.batched(batchedOps => { | ||
// batchedOps is able to keep track of mutated | ||
// objects. | ||
return compose( | ||
batchedOps.push(5), | ||
batchedOps.push(4) | ||
)(arr); | ||
}); | ||
expect(sameResult).to.deep.equal([1, 2, 3, 4, 5]); | ||
``` | ||
@@ -94,40 +86,56 @@ | ||
You can run operations in a mutation batch by calling `ops.batched(func)` with a function, and you can create a batch-wrapped function with `const batchedFunc = ops.batch(funcToWrap)`. | ||
A batch token is supplied by the user at the start of a batch, or created by `immutable-ops`. Each newly created object within a batch is tagged with that token. If a batch using token `X` operates on an object that is tagged with token `X`, it is free to mutate it. You can think of it as an ownership; the batch owns the newly created object and therefore is free to mutate it. New batches use a token `Y` that will never be equal to the previous token. | ||
When `immutable-ops` creates a new object or array during batched mutations to preserve immutability, it tags it as a mutable object (by adding an unenumerable `@@_____canMutate` property) and pushes its reference to an array of `mutatedObjects`. All consecutive functions applied will execute a mutating operations for objects that have the tag. This applies for tagged objects found in nested structures too. | ||
Tags are not removed; They are assigned to a non-enumerable property `@@_______immutableOpsOwnerID` which should avoid any collisions. | ||
When the function finishes executing, `immutable-ops` loops through the `mutatedObjects` array, removing the tag properties from each object, and clearing the `mutatedObjects` array. | ||
This token strategy is similar to what ImmutableJS uses to track batches. | ||
The overhead of keeping track of mutated objects should be a sufficient tradeoff to creating lots of new objects to applyi multiple consecutive operations, unless you're working on a really big set of data. | ||
**Manually using batch tokens** | ||
## Currying | ||
`ops.batch` gives you access to all the `immutable-ops` functions that take a token as their additional first argument. Otherwise they are identical to the functions found in `ops` directly. | ||
All operations are curried by default. If you don't want them to be curried, pass `{ curried: false }` to `getImmutableOps()`. Functions are curried with `ramda.curry`. In addition to normal currying behaviour, you can use the `ramda` placeholder variable available in `ops.__` to specify parameters you want to pass arguments for later. Example: | ||
```javascript | ||
const removeNFromHead = ops.splice(/* startIndex */ 0, /* deleteCount */ops.__, /* valsToAdd */[]); | ||
const removeTwoFromHead = removeNFromHead(2); | ||
const arr = [1, 2, 3]; | ||
import ops from 'immutable-ops'; | ||
const token = ops.getBatchToken(); | ||
console.log(removeTwoFromHead(arr)); | ||
// [3]; | ||
// This object has no batch token, since it was not created by immutable-ops. | ||
const obj = {a: 1, b: 2}; | ||
// obj2 is a newly created object tagged with the token. | ||
const obj2 = ops.batch.set(token, 'a', 10, obj); | ||
expect(obj).to.not.equal(obj2) | ||
// Because we operate on obj2 that has the same token as | ||
// we passed to the function, obj2 is mutated. | ||
const obj3 = ops.batch.set(token, 'b', 20, obj2); | ||
expect(obj2).to.equal(obj3); | ||
``` | ||
## Batched Mutations API | ||
### batched(functionToRun) | ||
**Handling batch tokens implicitly** | ||
Executes `functionToRun` as a batched mutation and returns the return value of `functionToRun`.`functionToRun` will be called without arguments. During `functionToRun` execution, `immutable-ops` will keep track of new objects created during operations, and apply further operations with mutations to those objects. | ||
```javascript | ||
import ops from 'immutable-ops'; | ||
### batch(functionToWrap) | ||
const obj = {a: 1, b: 2}; | ||
Like `batched`, but returns a function that wraps `functionToWrap` to be executed as a batch. `functionToWrap` is also curried. When `functionToWrap` is executed (all arguments are passed), all operations run during its execution will apply mutations instead of creating new objects whenever possible. | ||
const obj3 = ops.batched(batchedOps => { | ||
// batchedOps has functions that are bound to a new batch token. | ||
const obj2 = batchedOps.set('a', 10, obj); | ||
return batchedOps.set('b', 20, obj2); | ||
}); | ||
``` | ||
### open() | ||
## Currying | ||
Opens a batch session. From this point on, any operations done through the `ops` instance that `open` was called from will be applied mutatively **if** the object it's operating on was created after opening the session. | ||
All operations are curried by default. Functions are curried with `ramda.curry`. In addition to normal currying behaviour, you can use the `ramda` placeholder variable available in `ops.__` to specify parameters you want to pass arguments for later. Example: | ||
### close() | ||
```javascript | ||
const removeNFromHead = ops.splice(/* startIndex */ 0, /* deleteCount */ops.__, /* valsToAdd */[]); | ||
const removeTwoFromHead = removeNFromHead(2); | ||
const arr = [1, 2, 3]; | ||
Closes the current batch session. | ||
console.log(removeTwoFromHead(arr)); | ||
// [3]; | ||
``` | ||
@@ -318,4 +326,21 @@ ## Object API | ||
## Changelog | ||
## 0.5.0: Major Changes | ||
- **BREAKING**: No `getImmutableOps` function, which was the main export, is exported anymore because options were removed. Now the object containing the operation functions is exported directly. | ||
- **BREAKING**: removed option to choose whether operations are curried. Functions are now always curried. | ||
- **BREAKING**: former batched mutations API totally replaced. | ||
- **BREAKING**: batched mutations implementation changed. | ||
Previously newly created objects were tagged with a "can mutate" tag, and references to those objects were kept in a list. After the batch was finished, the list was processed by removing the tags from each object in the list. | ||
Now a batch token is created at the start of a batch (or supplied by the user). Each newly created object is tagged with that token. If a batch using token `X` operates on an object that is tagged with token `X`, it is free to mutate it. New batches use a token `Y` that will never be equal to the previous token. | ||
Tags are not removed anymore; They are assigned to a non-enumerable property `@@_______immutableOpsOwnerID` which should avoid any collisions. | ||
This token strategy is similar to what ImmutableJS uses to track batches. | ||
## License | ||
MIT. See `LICENSE` |
305
src/index.js
@@ -1,9 +0,22 @@ | ||
import forOwn from 'lodash/forOwn'; | ||
import isArrayLike from 'lodash/isArrayLike'; | ||
import curry from 'ramda/src/curry'; | ||
import wrap from 'ramda/src/wrap'; | ||
import placeholder from 'ramda/src/__'; | ||
const MUTABILITY_TAG = '@@_______canMutate'; | ||
function forOwn(obj, fn) { | ||
for (const key in obj) { | ||
if (obj.hasOwnProperty(key)) { | ||
fn(obj[key], key); | ||
} | ||
} | ||
} | ||
function isArrayLike(value) { | ||
return value | ||
&& typeof value === 'object' | ||
&& typeof value.length === 'number' | ||
&& value.length >= 0 | ||
&& value.length % 1 === 0; | ||
} | ||
const OWNER_ID_TAG = '@@_______immutableOpsOwnerID'; | ||
function fastArrayCopy(arr) { | ||
@@ -17,9 +30,16 @@ const copied = new Array(arr.length); | ||
export function canMutate(obj) { | ||
return obj.hasOwnProperty(MUTABILITY_TAG); | ||
export function canMutate(obj, ownerID) { | ||
if (!ownerID) return false; | ||
return obj[OWNER_ID_TAG] === ownerID; | ||
} | ||
function addCanMutateTag(opts, obj) { | ||
Object.defineProperty(obj, MUTABILITY_TAG, { | ||
value: true, | ||
const newOwnerID = typeof Symbol === 'function' | ||
? () => Symbol('ownerID') | ||
: () => ({}); | ||
export const getBatchToken = newOwnerID; | ||
function addOwnerID(obj, ownerID) { | ||
Object.defineProperty(obj, OWNER_ID_TAG, { | ||
value: ownerID, | ||
configurable: true, | ||
@@ -29,17 +49,9 @@ enumerable: false, | ||
opts.batchManager.addMutated(obj); | ||
return obj; | ||
} | ||
function removeCanMutateTag(obj) { | ||
delete obj[MUTABILITY_TAG]; | ||
return obj; | ||
} | ||
function prepareNewObject(opts, instance) { | ||
if (opts.batchManager.isWithMutations()) { | ||
addCanMutateTag(opts, instance); | ||
function prepareNewObject(instance, ownerID) { | ||
if (ownerID) { | ||
addOwnerID(instance, ownerID); | ||
} | ||
opts.createdObjects++; | ||
return instance; | ||
@@ -68,3 +80,3 @@ } | ||
function mutableSet(opts, key, value, obj) { | ||
function mutableSet(key, value, obj) { | ||
obj[key] = value; | ||
@@ -74,3 +86,3 @@ return obj; | ||
function mutableSetIn(opts, _pathArg, value, obj) { | ||
function mutableSetIn(_pathArg, value, obj) { | ||
const originalPathArg = normalizePath(_pathArg); | ||
@@ -89,3 +101,3 @@ | ||
const newObj = {}; | ||
prepareNewObject(opts, newObj); | ||
prepareNewObject(newObj, null); | ||
acc[curr] = newObj; | ||
@@ -106,3 +118,3 @@ return newObj; | ||
function valueInPath(opts, _pathArg, obj) { | ||
function valueInPath(_pathArg, obj) { | ||
const pathArg = normalizePath(_pathArg); | ||
@@ -126,10 +138,17 @@ | ||
function immutableSetIn(opts, _pathArg, value, obj) { | ||
function immutableSetIn(ownerID, _pathArg, value, obj) { | ||
const pathArg = normalizePath(_pathArg); | ||
const currentValue = valueInPath(opts, pathArg, obj); | ||
const currentValue = valueInPath(pathArg, obj); | ||
if (value === currentValue) return obj; | ||
const pathLen = pathArg.length; | ||
let acc = Object.assign(prepareNewObject(opts, {}), obj); | ||
let acc; | ||
if (canMutate(obj, ownerID)) { | ||
acc = obj; | ||
} else { | ||
acc = Object.assign(prepareNewObject({}, ownerID), obj); | ||
} | ||
const rootObj = acc; | ||
@@ -147,6 +166,6 @@ | ||
if (currType === 'object') { | ||
if (canMutate(currRef)) { | ||
if (canMutate(currRef, ownerID)) { | ||
acc = currRef; | ||
} else { | ||
const newObj = prepareNewObject(opts, {}); | ||
const newObj = prepareNewObject({}, ownerID); | ||
acc[curr] = Object.assign(newObj, currRef); | ||
@@ -159,3 +178,3 @@ acc = newObj; | ||
if (currType === 'undefined') { | ||
const newObj = prepareNewObject(opts, {}); | ||
const newObj = prepareNewObject({}, ownerID); | ||
acc[curr] = newObj; | ||
@@ -173,6 +192,6 @@ acc = newObj; | ||
function mutableMerge(isDeep, opts, _mergeObjs, baseObj) { | ||
function mutableMerge(isDeep, _mergeObjs, baseObj) { | ||
const mergeObjs = forceArray(_mergeObjs); | ||
if (opts.deep) { | ||
if (isDeep) { | ||
mergeObjs.forEach(mergeObj => { | ||
@@ -183,5 +202,3 @@ forOwn(mergeObj, (value, key) => { | ||
if (typeof value === 'object') { | ||
assignValue = canMutate(value) | ||
? mutableMerge(isDeep, opts, [value], baseObj[key]) | ||
: immutableMerge(isDeep, opts, [value], baseObj[key]); // eslint-disable-line | ||
assignValue = mutableMerge(isDeep, [value], baseObj[key]); | ||
} else { | ||
@@ -207,3 +224,3 @@ assignValue = value; | ||
function mutableOmit(opts, _keys, obj) { | ||
function mutableOmit(_keys, obj) { | ||
const keys = forceArray(_keys); | ||
@@ -220,4 +237,4 @@ keys.forEach(key => { | ||
function immutableMerge(isDeep, opts, _mergeObjs, obj) { | ||
if (canMutate(obj)) return mutableMerge(isDeep, opts, _mergeObjs, obj); | ||
function immutableMerge(isDeep, ownerID, _mergeObjs, obj) { | ||
if (canMutate(obj, ownerID)) return mutableMerge(isDeep, _mergeObjs, obj); | ||
const mergeObjs = forceArray(_mergeObjs); | ||
@@ -232,3 +249,3 @@ | ||
nextObject = Object.assign({}, obj); | ||
prepareNewObject(opts, nextObject); | ||
prepareNewObject(nextObject, ownerID); | ||
} | ||
@@ -243,3 +260,3 @@ }; | ||
if (_shouldMergeKey(nextObject, mergeObj, key)) { | ||
const recursiveMergeResult = immutableMerge(isDeep, opts, mergeValue, currentValue); | ||
const recursiveMergeResult = immutableMerge(isDeep, ownerID, mergeValue, currentValue); | ||
@@ -267,4 +284,4 @@ if (recursiveMergeResult !== currentValue) { | ||
function immutableArrSet(opts, index, value, arr) { | ||
if (canMutate(arr)) return mutableSet(opts, index, value, arr); | ||
function immutableArrSet(ownerID, index, value, arr) { | ||
if (canMutate(arr, ownerID)) return mutableSet(index, value, arr); | ||
@@ -275,3 +292,3 @@ if (arr[index] === value) return arr; | ||
newArr[index] = value; | ||
prepareNewObject(opts, newArr); | ||
prepareNewObject(newArr, ownerID); | ||
@@ -281,5 +298,5 @@ return newArr; | ||
function immutableSet(opts, key, value, obj) { | ||
if (isArrayLike(obj)) return immutableArrSet(opts, key, value, obj); | ||
if (canMutate(obj)) return mutableSet(opts, key, value, obj); | ||
function immutableSet(ownerID, key, value, obj) { | ||
if (isArrayLike(obj)) return immutableArrSet(ownerID, key, value, obj); | ||
if (canMutate(obj, ownerID)) return mutableSet(key, value, obj); | ||
@@ -289,3 +306,3 @@ if (obj[key] === value) return obj; | ||
const newObj = Object.assign({}, obj); | ||
prepareNewObject(opts, newObj); | ||
prepareNewObject(newObj, ownerID); | ||
newObj[key] = value; | ||
@@ -295,4 +312,4 @@ return newObj; | ||
function immutableOmit(opts, _keys, obj) { | ||
if (canMutate(obj)) return mutableOmit(opts, _keys, obj); | ||
function immutableOmit(ownerID, _keys, obj) { | ||
if (canMutate(obj, ownerID)) return mutableOmit(_keys, obj); | ||
@@ -309,7 +326,7 @@ const keys = forceArray(_keys); | ||
}); | ||
prepareNewObject(opts, newObj); | ||
prepareNewObject(newObj, ownerID); | ||
return newObj; | ||
} | ||
function mutableArrPush(opts, _vals, arr) { | ||
function mutableArrPush(_vals, arr) { | ||
const vals = forceArray(_vals); | ||
@@ -320,3 +337,3 @@ arr.push(...vals); | ||
function mutableArrFilter(opts, func, arr) { | ||
function mutableArrFilter(func, arr) { | ||
let currIndex = 0; | ||
@@ -337,3 +354,3 @@ let originalIndex = 0; | ||
function mutableArrSplice(opts, index, deleteCount, _vals, arr) { | ||
function mutableArrSplice(index, deleteCount, _vals, arr) { | ||
const vals = forceArray(_vals); | ||
@@ -344,12 +361,12 @@ arr.splice(index, deleteCount, ...vals); | ||
function mutableArrInsert(opts, index, _vals, arr) { | ||
return mutableArrSplice(opts, index, 0, _vals, arr); | ||
function mutableArrInsert(index, _vals, arr) { | ||
return mutableArrSplice(index, 0, _vals, arr); | ||
} | ||
function immutableArrSplice(opts, index, deleteCount, _vals, arr) { | ||
if (canMutate(arr)) return mutableArrSplice(opts, index, deleteCount, _vals, arr); | ||
function immutableArrSplice(ownerID, index, deleteCount, _vals, arr) { | ||
if (canMutate(arr, ownerID)) return mutableArrSplice(index, deleteCount, _vals, arr); | ||
const vals = forceArray(_vals); | ||
const newArr = arr.slice(); | ||
prepareNewObject(opts, newArr); | ||
prepareNewObject(newArr, ownerID); | ||
newArr.splice(index, deleteCount, ...vals); | ||
@@ -360,13 +377,13 @@ | ||
function immutableArrInsert(opts, index, _vals, arr) { | ||
if (canMutate(arr)) return mutableArrInsert(opts, index, _vals, arr); | ||
return immutableArrSplice(opts, index, 0, _vals, arr); | ||
function immutableArrInsert(ownerID, index, _vals, arr) { | ||
if (canMutate(arr, ownerID)) return mutableArrInsert(index, _vals, arr); | ||
return immutableArrSplice(ownerID, index, 0, _vals, arr); | ||
} | ||
function immutableArrPush(opts, vals, arr) { | ||
return immutableArrInsert(opts, arr.length, vals, arr); | ||
function immutableArrPush(ownerID, vals, arr) { | ||
return immutableArrInsert(ownerID, arr.length, vals, arr); | ||
} | ||
function immutableArrFilter(opts, func, arr) { | ||
if (canMutate(arr)) return mutableArrFilter(opts, func, arr); | ||
function immutableArrFilter(ownerID, func, arr) { | ||
if (canMutate(arr, ownerID)) return mutableArrFilter(func, arr); | ||
const newArr = arr.filter(func); | ||
@@ -376,7 +393,7 @@ | ||
prepareNewObject(opts, newArr); | ||
prepareNewObject(newArr, ownerID); | ||
return newArr; | ||
} | ||
const operations = { | ||
const immutableOperations = { | ||
// object operations | ||
@@ -396,120 +413,68 @@ merge: immutableShallowMerge, | ||
set: immutableSet, | ||
}; | ||
mutable: { | ||
// object operations | ||
merge: mutableShallowMerge, | ||
deepMerge: mutableDeepMerge, | ||
omit: mutableOmit, | ||
setIn: mutableSetIn, | ||
const mutableOperations = { | ||
// object operations | ||
merge: mutableShallowMerge, | ||
deepMerge: mutableDeepMerge, | ||
omit: mutableOmit, | ||
setIn: mutableSetIn, | ||
// array operations | ||
insert: mutableArrInsert, | ||
push: mutableArrPush, | ||
filter: mutableArrFilter, | ||
splice: mutableArrSplice, | ||
// array operations | ||
insert: mutableArrInsert, | ||
push: mutableArrPush, | ||
filter: mutableArrFilter, | ||
splice: mutableArrSplice, | ||
// both | ||
set: mutableSet, | ||
}, | ||
// both | ||
set: mutableSet, | ||
}; | ||
function bindOperationsToOptions(opsObj, opts) { | ||
const boundOperations = {}; | ||
forOwn(opsObj, (value, key) => { | ||
if (typeof value === 'object') { | ||
boundOperations[key] = bindOperationsToOptions(value, opts); | ||
} else { | ||
boundOperations[key] = value.bind(null, opts); | ||
export function getImmutableOps() { | ||
const immutableOps = Object.assign({}, immutableOperations); | ||
forOwn(immutableOps, (value, key) => { | ||
immutableOps[key] = curry(value.bind(null, null)); | ||
}); | ||
if (opts.curried) { | ||
boundOperations[key] = curry(boundOperations[key]); | ||
} | ||
} | ||
const mutableOps = Object.assign({}, mutableOperations); | ||
forOwn(mutableOps, (value, key) => { | ||
mutableOps[key] = curry(value); | ||
}); | ||
return boundOperations; | ||
} | ||
const batchOps = Object.assign({}, immutableOperations); | ||
forOwn(batchOps, (value, key) => { | ||
batchOps[key] = curry(value); | ||
}); | ||
function getBatchManager() { | ||
const previousSessionStack = []; | ||
let currMutatedObjects = null; | ||
let objectsCreated = 0; | ||
function batched(_token, _fn) { | ||
let token; | ||
let fn; | ||
return { | ||
open() { | ||
if (currMutatedObjects !== null) { | ||
previousSessionStack.push(currMutatedObjects); | ||
} | ||
currMutatedObjects = []; | ||
}, | ||
if (typeof _token === 'function') { | ||
fn = _token; | ||
token = getBatchToken(); | ||
} else { | ||
token = _token; | ||
fn = _fn; | ||
} | ||
isWithMutations() { | ||
return currMutatedObjects !== null; | ||
}, | ||
const immutableOpsBoundToToken = Object.assign({}, immutableOperations); | ||
forOwn(immutableOpsBoundToToken, (value, key) => { | ||
immutableOpsBoundToToken[key] = curry(value.bind(null, token)); | ||
}); | ||
return fn(immutableOpsBoundToToken); | ||
} | ||
addMutated(obj) { | ||
currMutatedObjects.push(obj); | ||
objectsCreated++; | ||
}, | ||
getMutatedObjects() { | ||
return currMutatedObjects; | ||
}, | ||
getObjectsCreatedCount() { | ||
return objectsCreated; | ||
}, | ||
close() { | ||
if (currMutatedObjects !== null) { | ||
currMutatedObjects.forEach(removeCanMutateTag); | ||
if (previousSessionStack.length) { | ||
currMutatedObjects = previousSessionStack.pop(); | ||
} else { | ||
currMutatedObjects = null; | ||
} | ||
objectsCreated = 0; | ||
} | ||
}, | ||
}; | ||
return Object.assign(immutableOps, { | ||
mutable: mutableOps, | ||
batch: batchOps, | ||
batched, | ||
__: placeholder, | ||
getBatchToken, | ||
}); | ||
} | ||
export default function getImmutableOps(userOpts) { | ||
const defaultOpts = { | ||
curried: true, | ||
batchManager: getBatchManager(), | ||
}; | ||
export const ops = getImmutableOps(); | ||
const opts = Object.assign({ createdObjects: 0 }, defaultOpts, (userOpts || {})); | ||
const boundOperations = bindOperationsToOptions(operations, opts); | ||
function batchWrapper() { | ||
const func = arguments[0]; | ||
const args = Array.prototype.slice.call(arguments, 1); | ||
opts.batchManager.open(); | ||
const returnValue = func.apply(null, args); | ||
opts.batchManager.close(); | ||
return returnValue; | ||
} | ||
boundOperations.batched = batchWrapper; | ||
boundOperations.batch = wrap(placeholder, batchWrapper); | ||
boundOperations.createdObjectsCount = () => opts.createdObjects; | ||
boundOperations.getMutatedObjects = opts.batchManager.getMutatedObjects; | ||
boundOperations.__ = placeholder; | ||
boundOperations.open = opts.batchManager.open; | ||
boundOperations.close = opts.batchManager.close; | ||
boundOperations.getBatchManager = getBatchManager; | ||
boundOperations.useBatchManager = manager => { | ||
opts.batchManager.close(); | ||
opts.batchManager = manager; | ||
boundOperations.open = manager.open; | ||
boundOperations.close = manager.close; | ||
boundOperations.getMutatedObjects = manager.getMutatedObjects; | ||
}; | ||
return boundOperations; | ||
} | ||
export default ops; |
import chai from 'chai'; | ||
import sinonChai from 'sinon-chai'; | ||
import getOps, { canMutate } from '../index'; | ||
import freeze from 'deep-freeze'; | ||
import compose from 'ramda/src/compose'; | ||
import { ops, canMutate, getBatchToken } from '../index'; | ||
chai.use(sinonChai); | ||
const { expect } = chai; | ||
describe('operations', () => { | ||
let ops; | ||
describe('batched', () => { | ||
it('works', () => { | ||
const res = ops.batched(batchOps => { | ||
const obj = {}; | ||
const result = batchOps.set('a', 1, obj); | ||
expect(result).to.deep.equal({ a: 1 }); | ||
expect(result).not.to.equal(obj); | ||
beforeEach(() => { | ||
ops = getOps(); | ||
}); | ||
it('wrapBatched', () => { | ||
const pushFour = ops.push(4); | ||
const pushFive = ops.push(5); | ||
const arr = freeze([1, 2, 3]); | ||
const pusher = ops.batch(compose(pushFive, pushFour)); | ||
expect(pusher).to.be.a('function'); | ||
const result = pusher(arr); | ||
expect(result).to.deep.equal([1, 2, 3, 4, 5]); | ||
}); | ||
it('useBatchManager', () => { | ||
const myManager = ops.getBatchManager(); | ||
ops.useBatchManager(myManager); | ||
const pusher = ops.push(0); | ||
const batchOperation = ops.batch((arr) => { | ||
const first = pusher(arr); | ||
expect(ops.getMutatedObjects()).to.equal(myManager.getMutatedObjects()); | ||
expect(ops.getMutatedObjects()[0]).to.equal(first); | ||
return first; | ||
const result2 = batchOps.omit('a', result); | ||
expect(result2).to.equal(result); | ||
expect(result2).to.deep.equal({}); | ||
return result2; | ||
}); | ||
batchOperation([]); | ||
expect(res).to.deep.equal({}); | ||
}); | ||
}); | ||
describe('operations', () => { | ||
describe('object', () => { | ||
describe('batched mutations', () => { | ||
const token = getBatchToken(); | ||
it('deepMerges', () => { | ||
@@ -63,12 +49,9 @@ const baseObj = freeze({ | ||
}); | ||
let result; | ||
const merger = ops.deepMerge(mergeObj); | ||
ops.batched(() => { | ||
result = merger(baseObj); | ||
expect(canMutate(result)).to.be.true; | ||
expect(canMutate(result.deeper)).to.be.true; | ||
}); | ||
const merger = ops.batch.deepMerge(token, mergeObj); | ||
const result = merger(baseObj); | ||
expect(canMutate(result, token)).to.be.true; | ||
expect(canMutate(result.deeper, token)).to.be.true; | ||
expect(canMutate(result)).to.be.false; | ||
expect(canMutate(result.deeper)).to.be.false; | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(canMutate(result.deeper, getBatchToken())).to.be.false; | ||
@@ -93,12 +76,12 @@ expect(result).to.not.equal(baseObj); | ||
let result; | ||
const omitter = ops.omit('age'); | ||
const omitter = ops.batch.omit(token, 'age'); | ||
ops.batched(() => { | ||
result = omitter(obj); | ||
expect(canMutate(result)).to.be.true; | ||
}); | ||
const result = omitter(obj); | ||
expect(canMutate(result, token)).to.be.true; | ||
expect(canMutate(result)).to.be.false; | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(result).to.not.contain.keys(['age']); | ||
// Further modification should mutate the existing object. | ||
expect(ops.batch.omit(token, 'name', result)).to.equal(result); | ||
}); | ||
@@ -112,13 +95,12 @@ | ||
let result; | ||
const omitter = ops.batch.omit(token, ['age']); | ||
const result = omitter(obj); | ||
const omitter = ops.omit(['age']); | ||
ops.batched(() => { | ||
result = omitter(obj); | ||
expect(canMutate(result, token)).to.be.true; | ||
expect(canMutate(result)).to.be.true; | ||
}); | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(result).to.not.contain.keys(['age']); | ||
expect(canMutate(result)).to.be.false; | ||
expect(result).to.not.contain.keys(['age']); | ||
// Further modification should mutate the existing object. | ||
expect(ops.batch.omit(token, ['name'], result)).to.equal(result); | ||
}); | ||
@@ -133,10 +115,7 @@ | ||
let result; | ||
ops.batched(() => { | ||
result = ops.set('two', 5, obj); | ||
const result = ops.batch.set(token, 'two', 5, obj); | ||
expect(canMutate(result)).to.be.true; | ||
result = ops.set('two', 2, result); | ||
}); | ||
expect(result).to.deep.equal({ | ||
expect(canMutate(result, token)).to.be.true; | ||
const result2 = ops.batch.set(token, 'two', 2, result); | ||
expect(result2).to.deep.equal({ | ||
one: 1, | ||
@@ -146,2 +125,4 @@ two: 2, | ||
}); | ||
expect(result).to.equal(result2); | ||
}); | ||
@@ -160,13 +141,9 @@ | ||
}); | ||
let result; | ||
const setter = ops.setIn('first.second.value', 'anotherValue'); | ||
const setter = ops.batch.setIn(token, 'first.second.value', 'anotherValue'); | ||
ops.batched(() => { | ||
result = setter(obj); | ||
const result = setter(obj); | ||
expect(canMutate(result, token)).to.be.true; | ||
expect(canMutate(result)).to.be.true; | ||
}); | ||
expect(canMutate(result)).to.be.false; | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(result).not.to.equal(obj); | ||
@@ -177,2 +154,6 @@ expect(result.first.second.value).to.equal('anotherValue'); | ||
expect(result.first.second.maintain).to.be.true; | ||
const result2 = ops.batch.setIn(token, 'first.second.value', 'secondAnotherValue', result); | ||
expect(result).to.equal(result2); | ||
expect(result2.first.second.value).to.equal('secondAnotherValue'); | ||
}); | ||
@@ -335,9 +316,11 @@ }); | ||
describe('array', () =>{ | ||
describe('array', () => { | ||
describe('batched mutations', () => { | ||
const token = getBatchToken(); | ||
it('push', () => { | ||
const push = ops.push; | ||
const push = ops.batch.push; | ||
const arr = freeze([5, 4]); | ||
const pusher = push(freeze([1, 2, 3])); | ||
const result = ops.batched(() => pusher(arr)); | ||
const pusher = push(token, freeze([1, 2, 3])); | ||
const result = pusher(arr); | ||
@@ -347,11 +330,19 @@ expect(result).to.not.equal(arr); | ||
expect(result).to.deep.equal([5, 4, 1, 2, 3]); | ||
const result2 = push(token, [4, 5], result); | ||
expect(result).to.equal(result2); | ||
expect(result2).to.deep.equal([5, 4, 1, 2, 3, 4, 5]); | ||
}); | ||
it('insert', () => { | ||
const insert = ops.insert; | ||
const insert = ops.batch.insert; | ||
const arr = freeze([1, 2, 5]); | ||
const inserter = insert(2, freeze([3, 4])); | ||
const result = ops.batched(() => inserter(arr)); | ||
const inserter = insert(token, 2, freeze([3, 4])); | ||
const result = inserter(arr); | ||
expect(result).to.deep.equal([1, 2, 3, 4, 5]); | ||
const result2 = ops.batch.insert(token, 2, [1000], result); | ||
expect(result).to.equal(result2); | ||
expect(result2).to.deep.equal([1, 2, 1000, 3, 4, 5]); | ||
}); | ||
@@ -361,11 +352,11 @@ | ||
const arr = freeze([0, 1, 2, 3]); | ||
let result; | ||
const result = ops.batch.filter(token, item => item % 2 === 0, arr); | ||
expect(canMutate(result, token)).to.be.true; | ||
ops.batched(() => { | ||
result = ops.filter(item => item % 2 === 0, arr); | ||
expect(canMutate(result)).to.be.true; | ||
}); | ||
expect(result).to.deep.equal([0, 2]); | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(result).to.deep.equal([0, 2]); | ||
expect(canMutate(result)).to.be.false; | ||
const result2 = ops.batch.filter(token, item => item === 2, result); | ||
expect(result2).to.equal(result); | ||
expect(result2).to.deep.equal([2]); | ||
}); | ||
@@ -376,31 +367,40 @@ | ||
const result = ops.batched(() => { | ||
const setter = ops.set(2, 3); | ||
const res = setter(arr); | ||
expect(canMutate(res)).to.be.true; | ||
return res; | ||
}); | ||
const setter = ops.batch.set(token, 2, 3); | ||
const result = setter(arr); | ||
expect(canMutate(result, token)).to.be.true; | ||
expect(canMutate(result)).to.be.false; | ||
expect(canMutate(result, getBatchToken())).to.be.false; | ||
expect(result).to.deep.equal([1, 2, 3, 4]); | ||
const result2 = ops.batch.set(token, 2, 1000, result); | ||
expect(result).to.equal(result2); | ||
expect(result2).to.deep.equal([1, 2, 1000, 4]); | ||
}); | ||
it('splice with deletions', () => { | ||
const splice = ops.splice; | ||
const splice = ops.batch.splice; | ||
const arr = freeze([1, 2, 3, 3, 3, 4]); | ||
const splicer = splice(2, 2, []); | ||
const splicer = splice(token, 2, 2, []); | ||
const result = ops.batched(() => splicer(arr)); | ||
const result = splicer(arr); | ||
expect(result).to.deep.equal([1, 2, 3, 4]); | ||
const result2 = ops.batch.splice(token, 2, 1, [], result); | ||
expect(result2).to.equal(result); | ||
expect(result2).to.deep.equal([1, 2, 4]); | ||
}); | ||
it('splice with additions', () => { | ||
const splice = ops.splice; | ||
const splice = ops.batch.splice; | ||
const arr = freeze([1, 5]); | ||
const splicer = splice(1, 0, [2, 3, 4]); | ||
const splicer = splice(token, 1, 0, [2, 3, 4]); | ||
const result = ops.batched(() => splicer(arr)); | ||
const result = splicer(arr); | ||
expect(result).to.deep.equal([1, 2, 3, 4, 5]); | ||
const result2 = ops.batch.splice(token, 0, 1, [1000], result); | ||
expect(result).to.equal(result2); | ||
expect(result2).to.deep.equal([1000, 2, 3, 4, 5]); | ||
}); | ||
@@ -407,0 +407,0 @@ }); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
88139
1
14
342
11
1464
+ Addedramda@0.22.1(transitive)
- Removedlodash@^4.2.1
- Removedlodash@4.17.21(transitive)
- Removedramda@0.19.1(transitive)
Updatedramda@^0.22.1